From 748bd206d1666e56382be388d87d649ae39fe413 Mon Sep 17 00:00:00 2001 From: Untone Date: Sat, 17 Feb 2024 16:03:47 +0300 Subject: [PATCH 01/33] small-fixes --- src/components/Nav/Header/Link.tsx | 4 ++-- src/context/editor.tsx | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/Nav/Header/Link.tsx b/src/components/Nav/Header/Link.tsx index c041c4d0..6bf639b5 100644 --- a/src/components/Nav/Header/Link.tsx +++ b/src/components/Nav/Header/Link.tsx @@ -17,11 +17,11 @@ type Props = { export const Link = (props: Props) => { const { page } = useRouter() - const isSelected = page().route === props.routeName + const isSelected = page()?.route === props.routeName return (
  • { export const EditorProvider = (props: { children: JSX.Element }) => { const { t } = useLocalize() const { page } = useRouter() + const { author } = useSession() const { showSnackbar } = useSnackbar() const [isEditorPanelVisible, setIsEditorPanelVisible] = createSignal(false) const editorRef: { current: () => Editor } = { current: null } @@ -132,7 +134,7 @@ export const EditorProvider = (props: { children: JSX.Element }) => { lead: formToUpdate.lead, description: formToUpdate.description, cover: formToUpdate.coverImageUrl, - media: formToUpdate.media, + media: formToUpdate.media }, publish, }) From 20e4e985f52b1f977577d61fa8a0fde187f787f5 Mon Sep 17 00:00:00 2001 From: Untone Date: Sat, 17 Feb 2024 16:25:25 +0300 Subject: [PATCH 02/33] fmt --- src/components/Nav/AuthModal/RegisterForm.tsx | 11 ++++++----- src/components/Nav/AuthModal/SendResetLinkForm.tsx | 10 ++++++++-- src/context/editor.tsx | 4 ++-- 3 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/components/Nav/AuthModal/RegisterForm.tsx b/src/components/Nav/AuthModal/RegisterForm.tsx index fd3b2467..a21818b1 100644 --- a/src/components/Nav/AuthModal/RegisterForm.tsx +++ b/src/components/Nav/AuthModal/RegisterForm.tsx @@ -152,9 +152,10 @@ export const RegisterForm = () => { ...prev, email: ( <> - {t('This email is registered')}. {t('You can')}{' '} + {t('This email is registered')} + {'. '} changeSearchParams({ mode: 'send-reset-link' })}> - {t('Set the new password').toLocaleLowerCase()} + {t('Set the new password')} ), @@ -196,7 +197,7 @@ export const RegisterForm = () => { disabled={Boolean(emailStatus())} placeholder={t('Full name')} autocomplete="one-time-code" - onInput={(event) => handleNameInput(event.currentTarget.value)} + onChange={(event) => handleNameInput(event.currentTarget.value)} /> @@ -227,8 +228,8 @@ export const RegisterForm = () => { setPasswordError(err)} - onInput={(value) => setPassword(value)} + errorMessage={(err) => !emailStatus() && setPasswordError(err)} + onInput={(value) => setPassword(emailStatus() ? '' : value)} />
    diff --git a/src/components/Nav/AuthModal/SendResetLinkForm.tsx b/src/components/Nav/AuthModal/SendResetLinkForm.tsx index b1880d9d..d3583791 100644 --- a/src/components/Nav/AuthModal/SendResetLinkForm.tsx +++ b/src/components/Nav/AuthModal/SendResetLinkForm.tsx @@ -1,7 +1,7 @@ import type { AuthModalSearchParams } from './types' import { clsx } from 'clsx' -import { JSX, Show, createSignal } from 'solid-js' +import { JSX, Show, createSignal, onMount } from 'solid-js' import { useLocalize } from '../../../context/localize' import { useSession } from '../../../context/session' @@ -72,6 +72,12 @@ export const SendResetLinkForm = () => { } } + onMount(() => { + if (email()) { + console.info('[SendResetLinkForm] email detected') + } + }) + return (
    { type="email" value={email()} placeholder={t('Email')} - onInput={(event) => handleEmailInput(event.currentTarget.value)} + onChange={(event) => handleEmailInput(event.currentTarget.value)} /> diff --git a/src/context/editor.tsx b/src/context/editor.tsx index 73e4bde1..8e8556f0 100644 --- a/src/context/editor.tsx +++ b/src/context/editor.tsx @@ -12,8 +12,8 @@ import { addArticles } from '../stores/zine/articles' import { slugify } from '../utils/slugify' import { useLocalize } from './localize' -import { useSnackbar } from './snackbar' import { useSession } from './session' +import { useSnackbar } from './snackbar' type WordCounter = { characters: number @@ -134,7 +134,7 @@ export const EditorProvider = (props: { children: JSX.Element }) => { lead: formToUpdate.lead, description: formToUpdate.description, cover: formToUpdate.coverImageUrl, - media: formToUpdate.media + media: formToUpdate.media, }, publish, }) From 6fa6076f9f911cbb467c785ab65ca922cbc2fdeb Mon Sep 17 00:00:00 2001 From: Untone Date: Sat, 17 Feb 2024 17:22:11 +0300 Subject: [PATCH 03/33] editor-context-fixes --- src/context/editor.tsx | 47 ++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/src/context/editor.tsx b/src/context/editor.tsx index 8e8556f0..5700ed05 100644 --- a/src/context/editor.tsx +++ b/src/context/editor.tsx @@ -12,7 +12,6 @@ import { addArticles } from '../stores/zine/articles' import { slugify } from '../utils/slugify' import { useLocalize } from './localize' -import { useSession } from './session' import { useSnackbar } from './snackbar' type WordCounter = { @@ -40,7 +39,7 @@ type EditorContextType = { wordCounter: Accessor form: ShoutForm formErrors: Record - editorRef: { current: () => Editor } + editorRef: { current: () => Editor | null } saveShout: (form: ShoutForm) => Promise saveDraft: (form: ShoutForm) => Promise saveDraftToLocalStorage: (form: ShoutForm) => void @@ -73,7 +72,7 @@ const saveDraftToLocalStorage = (formToSave: ShoutForm) => { localStorage.setItem(`shout-${formToSave.shoutId}`, JSON.stringify(formToSave)) } const getDraftFromLocalStorage = (shoutId: number) => { - return JSON.parse(localStorage.getItem(`shout-${shoutId}`)) + return JSON.parse(localStorage.getItem(`shout-${shoutId}`) || '') } const removeDraftFromLocalStorage = (shoutId: number) => { @@ -81,14 +80,19 @@ const removeDraftFromLocalStorage = (shoutId: number) => { } export const EditorProvider = (props: { children: JSX.Element }) => { - const { t } = useLocalize() + const localize = useLocalize() const { page } = useRouter() - const { author } = useSession() - const { showSnackbar } = useSnackbar() + const snackbar = useSnackbar() const [isEditorPanelVisible, setIsEditorPanelVisible] = createSignal(false) - const editorRef: { current: () => Editor } = { current: null } - const [form, setForm] = createStore(null) - const [formErrors, setFormErrors] = createStore>(null) + const editorRef: { current: () => Editor | null } = { current: () => null } + const [form, setForm] = createStore({ + body: '', + slug: '', + shoutId: 0, + title: '', + selectedTopics: [], + }) + const [formErrors, setFormErrors] = createStore({} as Record) const [wordCounter, setWordCounter] = createSignal({ characters: 0, words: 0, @@ -97,13 +101,16 @@ export const EditorProvider = (props: { children: JSX.Element }) => { const countWords = (value) => setWordCounter(value) const validate = () => { if (!form.title) { - setFormErrors('title', t('Please, set the article title')) + setFormErrors('title', localize?.t('Please, set the article title') || '') return false } - const parsedMedia = JSON.parse(form.media) + const parsedMedia = JSON.parse(form.media || '') if (form.layout === 'video' && !parsedMedia[0]) { - showSnackbar({ type: 'error', body: t('Looks like you forgot to upload the video') }) + snackbar?.showSnackbar({ + type: 'error', + body: localize?.t('Looks like you forgot to upload the video'), + }) return false } @@ -112,7 +119,7 @@ export const EditorProvider = (props: { children: JSX.Element }) => { const validateSettings = () => { if (form.selectedTopics.length === 0) { - setFormErrors('selectedTopics', t('Required')) + setFormErrors('selectedTopics', localize?.t('Required') || '') return false } @@ -145,11 +152,11 @@ export const EditorProvider = (props: { children: JSX.Element }) => { toggleEditorPanel() } - if (page().route === 'edit' && !validate()) { + if (page()?.route === 'edit' && !validate()) { return } - if (page().route === 'editSettings' && !validateSettings()) { + if (page()?.route === 'editSettings' && !validateSettings()) { return } @@ -164,7 +171,7 @@ export const EditorProvider = (props: { children: JSX.Element }) => { } } catch (error) { console.error('[saveShout]', error) - showSnackbar({ type: 'error', body: t('Error') }) + snackbar?.showSnackbar({ type: 'error', body: localize?.t('Error') || '' }) } } @@ -177,7 +184,7 @@ export const EditorProvider = (props: { children: JSX.Element }) => { toggleEditorPanel() } - if (page().route === 'edit') { + if (page()?.route === 'edit') { if (!validate()) { return } @@ -199,7 +206,7 @@ export const EditorProvider = (props: { children: JSX.Element }) => { openPage(router, 'feed') } catch (error) { console.error('[publishShout]', error) - showSnackbar({ type: 'error', body: t('Error') }) + snackbar?.showSnackbar({ type: 'error', body: localize?.t('Error') || '' }) } } @@ -217,7 +224,7 @@ export const EditorProvider = (props: { children: JSX.Element }) => { } } catch (error) { console.error('[publishShoutById]', error) - showSnackbar({ type: 'error', body: t('Error') }) + snackbar?.showSnackbar({ type: 'error', body: localize?.t('Error') }) } } @@ -228,7 +235,7 @@ export const EditorProvider = (props: { children: JSX.Element }) => { }) return true } catch { - showSnackbar({ type: 'error', body: t('Error') }) + snackbar?.showSnackbar({ type: 'error', body: localize?.t('Error') || '' }) return false } } From 002ffe64fc0fb7153e2d6566a8d486560d9fe28e Mon Sep 17 00:00:00 2001 From: Untone Date: Sat, 17 Feb 2024 17:28:57 +0300 Subject: [PATCH 04/33] parse-tolerate --- src/components/Article/FullArticle.tsx | 2 +- src/context/connect.tsx | 2 +- src/context/editor.tsx | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/Article/FullArticle.tsx b/src/components/Article/FullArticle.tsx index a8c54c98..0582010d 100644 --- a/src/components/Article/FullArticle.tsx +++ b/src/components/Article/FullArticle.tsx @@ -135,7 +135,7 @@ export const FullArticle = (props: Props) => { const media = createMemo(() => { try { - return JSON.parse(props.article.media) + return JSON.parse(props.article?.media || "[]") } catch { return [] } diff --git a/src/context/connect.tsx b/src/context/connect.tsx index d10d0695..41e1cab6 100644 --- a/src/context/connect.tsx +++ b/src/context/connect.tsx @@ -50,7 +50,7 @@ export const ConnectProvider = (props: { children: JSX.Element }) => { Authorization: token, }, onmessage(event) { - const m: SSEMessage = JSON.parse(event.data) + const m: SSEMessage = JSON.parse(event.data || "{}") console.log('[context.connect] Received message:', m) // Iterate over all registered handlers and call them diff --git a/src/context/editor.tsx b/src/context/editor.tsx index 5700ed05..9d40ec50 100644 --- a/src/context/editor.tsx +++ b/src/context/editor.tsx @@ -72,7 +72,7 @@ const saveDraftToLocalStorage = (formToSave: ShoutForm) => { localStorage.setItem(`shout-${formToSave.shoutId}`, JSON.stringify(formToSave)) } const getDraftFromLocalStorage = (shoutId: number) => { - return JSON.parse(localStorage.getItem(`shout-${shoutId}`) || '') + return JSON.parse(localStorage.getItem(`shout-${shoutId}`) || '{}') } const removeDraftFromLocalStorage = (shoutId: number) => { @@ -105,7 +105,7 @@ export const EditorProvider = (props: { children: JSX.Element }) => { return false } - const parsedMedia = JSON.parse(form.media || '') + const parsedMedia = JSON.parse(form.media || '[]') if (form.layout === 'video' && !parsedMedia[0]) { snackbar?.showSnackbar({ type: 'error', From e32e3d31ea4ad8ed2c03dad39acf4d739d96277f Mon Sep 17 00:00:00 2001 From: Untone Date: Sat, 17 Feb 2024 17:31:08 +0300 Subject: [PATCH 05/33] fmt --- src/components/Article/FullArticle.tsx | 2 +- src/context/connect.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Article/FullArticle.tsx b/src/components/Article/FullArticle.tsx index 0582010d..9d3f8c9d 100644 --- a/src/components/Article/FullArticle.tsx +++ b/src/components/Article/FullArticle.tsx @@ -135,7 +135,7 @@ export const FullArticle = (props: Props) => { const media = createMemo(() => { try { - return JSON.parse(props.article?.media || "[]") + return JSON.parse(props.article?.media || '[]') } catch { return [] } diff --git a/src/context/connect.tsx b/src/context/connect.tsx index 41e1cab6..dfd8d549 100644 --- a/src/context/connect.tsx +++ b/src/context/connect.tsx @@ -50,7 +50,7 @@ export const ConnectProvider = (props: { children: JSX.Element }) => { Authorization: token, }, onmessage(event) { - const m: SSEMessage = JSON.parse(event.data || "{}") + const m: SSEMessage = JSON.parse(event.data || '{}') console.log('[context.connect] Received message:', m) // Iterate over all registered handlers and call them From 3a6faa65a83785950a8286489d67586c1efd8874 Mon Sep 17 00:00:00 2001 From: Untone Date: Sat, 17 Feb 2024 17:40:10 +0300 Subject: [PATCH 06/33] tolerate-fails-more --- src/components/Article/FullArticle.tsx | 2 +- src/components/Feed/ArticleCard/ArticleCard.tsx | 2 +- src/context/editor.tsx | 2 +- src/utils/sortby.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/Article/FullArticle.tsx b/src/components/Article/FullArticle.tsx index 9d3f8c9d..ab14f1e6 100644 --- a/src/components/Article/FullArticle.tsx +++ b/src/components/Article/FullArticle.tsx @@ -77,7 +77,7 @@ export const FullArticle = (props: Props) => { const { t, formatDate, lang } = useLocalize() const { author, isAuthenticated, requireAuthentication } = useSession() - const formattedDate = createMemo(() => formatDate(new Date(props.article.published_at * 1000))) + const formattedDate = createMemo(() => formatDate(new Date((props.article?.published_at || 0) * 1000))) const canEdit = () => props.article.authors?.some((a) => Boolean(a) && a?.slug === author()?.slug) const mainTopic = createMemo(() => { diff --git a/src/components/Feed/ArticleCard/ArticleCard.tsx b/src/components/Feed/ArticleCard/ArticleCard.tsx index 31743db2..58f69cdf 100644 --- a/src/components/Feed/ArticleCard/ArticleCard.tsx +++ b/src/components/Feed/ArticleCard/ArticleCard.tsx @@ -117,7 +117,7 @@ export const ArticleCard = (props: ArticleCardProps) => { const { title, subtitle } = getTitleAndSubtitle(props.article) const formattedDate = createMemo(() => - props.article.published_at ? formatDate(new Date(props.article.published_at * 1000)) : '', + props.article?.published_at ? formatDate(new Date(props.article.published_at * 1000)) : '', ) const canEdit = () => diff --git a/src/context/editor.tsx b/src/context/editor.tsx index 9d40ec50..d2f1b82b 100644 --- a/src/context/editor.tsx +++ b/src/context/editor.tsx @@ -164,7 +164,7 @@ export const EditorProvider = (props: { children: JSX.Element }) => { const shout = await updateShout(formToSave, { publish: false }) removeDraftFromLocalStorage(formToSave.shoutId) - if (shout.published_at) { + if (shout?.published_at) { openPage(router, 'article', { slug: shout.slug }) } else { openPage(router, 'drafts') diff --git a/src/utils/sortby.ts b/src/utils/sortby.ts index 7566f577..9cd2182d 100644 --- a/src/utils/sortby.ts +++ b/src/utils/sortby.ts @@ -7,7 +7,7 @@ export const byCreated = (a: Shout | Reaction, b: Shout | Reaction) => { } export const byPublished = (a: Shout, b: Shout) => { - return a.published_at - b.published_at + return (a?.published_at || 0) - (b?.published_at || 0) } export const byLength = ( From 0dd2736dd518be3ddd6f92828d62b17bee166c5f Mon Sep 17 00:00:00 2001 From: Untone Date: Sat, 17 Feb 2024 18:03:01 +0300 Subject: [PATCH 07/33] catch-response-on-update --- .../Views/PublishSettings/PublishSettings.tsx | 8 ++--- src/context/editor.tsx | 31 ++++++++++++++----- src/graphql/client/core.ts | 4 +-- 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/src/components/Views/PublishSettings/PublishSettings.tsx b/src/components/Views/PublishSettings/PublishSettings.tsx index 7bc52d56..78dcdfbe 100644 --- a/src/components/Views/PublishSettings/PublishSettings.tsx +++ b/src/components/Views/PublishSettings/PublishSettings.tsx @@ -70,10 +70,10 @@ export const PublishSettings = (props: Props) => { return { coverImageUrl: props.form?.coverImageUrl, mainTopic: props.form?.mainTopic || EMPTY_TOPIC, - slug: props.form?.slug, - title: props.form?.title, - subtitle: props.form?.subtitle, - description: composeDescription(), + slug: props.form?.slug || '', + title: props.form?.title || '', + subtitle: props.form?.subtitle || '', + description: composeDescription() || '', selectedTopics: [], } }) diff --git a/src/context/editor.tsx b/src/context/editor.tsx index d2f1b82b..da11f919 100644 --- a/src/context/editor.tsx +++ b/src/context/editor.tsx @@ -161,7 +161,11 @@ export const EditorProvider = (props: { children: JSX.Element }) => { } try { - const shout = await updateShout(formToSave, { publish: false }) + const { shout, error } = await updateShout(formToSave, { publish: false }) + if (error) { + snackbar?.showSnackbar({ type: 'error', body: localize?.t(error) || '' }) + return + } removeDraftFromLocalStorage(formToSave.shoutId) if (shout?.published_at) { @@ -176,24 +180,33 @@ export const EditorProvider = (props: { children: JSX.Element }) => { } const saveDraft = async (draftForm: ShoutForm) => { - await updateShout(draftForm, { publish: false }) + const { error } = await updateShout(draftForm, { publish: false }) + if (error) { + snackbar?.showSnackbar({ type: 'error', body: localize?.t(error) || '' }) + return + } } const publishShout = async (formToPublish: ShoutForm) => { - if (isEditorPanelVisible()) { + const editorPanelVisible = isEditorPanelVisible() + const pageRoute = page()?.route + + if (editorPanelVisible) { toggleEditorPanel() } - if (page()?.route === 'edit') { + if (pageRoute === 'edit') { if (!validate()) { return } - await updateShout(formToPublish, { publish: false }) - const slug = slugify(form.title) setForm('slug', slug) openPage(router, 'editSettings', { shoutId: form.shoutId.toString() }) + const { error } = await updateShout(formToPublish, { publish: false }) + if (error) { + snackbar?.showSnackbar({ type: 'error', body: localize?.t(error) || '' }) + } return } @@ -202,7 +215,11 @@ export const EditorProvider = (props: { children: JSX.Element }) => { } try { - await updateShout(formToPublish, { publish: true }) + const { error } = await updateShout(formToPublish, { publish: true }) + if (error) { + snackbar?.showSnackbar({ type: 'error', body: localize?.t(error) || '' }) + return + } openPage(router, 'feed') } catch (error) { console.error('[publishShout]', error) diff --git a/src/graphql/client/core.ts b/src/graphql/client/core.ts index c80fe931..edf40567 100644 --- a/src/graphql/client/core.ts +++ b/src/graphql/client/core.ts @@ -157,12 +157,12 @@ export const apiClient = { shout_id: number shout_input?: ShoutInput publish: boolean - }): Promise => { + }): Promise => { const response = await apiClient.private .mutation(updateArticle, { shout_id, shout_input, publish }) .toPromise() console.debug('[graphql.client.core] updateArticle:', response.data) - return response.data.update_shout.shout + return response.data.update_shout }, deleteShout: async (params: MutationDelete_ShoutArgs): Promise => { const response = await apiClient.private.mutation(deleteShout, params).toPromise() From 560739627ad4f48dd6de861e990ea99aaf42b080 Mon Sep 17 00:00:00 2001 From: Untone Date: Sat, 17 Feb 2024 18:13:54 +0300 Subject: [PATCH 08/33] more-defined --- src/components/Views/EditView/EditView.tsx | 4 ++-- src/components/Views/PublishSettings/PublishSettings.tsx | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/Views/EditView/EditView.tsx b/src/components/Views/EditView/EditView.tsx index 4e862696..1969e338 100644 --- a/src/components/Views/EditView/EditView.tsx +++ b/src/components/Views/EditView/EditView.tsx @@ -307,10 +307,10 @@ export const EditView = (props: Props) => { subtitleInput.current = el }} allowEnterKey={false} - value={(value) => setForm('subtitle', value)} + value={(value) => setForm('subtitle', value || '')} class={styles.subtitleInput} placeholder={t('Subheader')} - initialValue={form.subtitle} + initialValue={form.subtitle || ''} maxLength={MAX_HEADER_LIMIT} /> diff --git a/src/components/Views/PublishSettings/PublishSettings.tsx b/src/components/Views/PublishSettings/PublishSettings.tsx index 78dcdfbe..47a2dee4 100644 --- a/src/components/Views/PublishSettings/PublishSettings.tsx +++ b/src/components/Views/PublishSettings/PublishSettings.tsx @@ -100,7 +100,7 @@ export const PublishSettings = (props: Props) => { const handleTopicSelectChange = (newSelectedTopics) => { if ( props.form.selectedTopics.length === 0 || - newSelectedTopics.every((topic) => topic.id !== props.form.mainTopic.id) + newSelectedTopics.every((topic) => topic.id !== props.form.mainTopic?.id) ) { setSettingsForm((prev) => { return { @@ -176,7 +176,7 @@ export const PublishSettings = (props: Props) => {
    {settingsForm.mainTopic.title}
    {settingsForm.title}
    -
    {settingsForm.subtitle}
    +
    {settingsForm.subtitle || ''}
    {author()?.name}
    @@ -203,7 +203,7 @@ export const PublishSettings = (props: Props) => { variant="bordered" fieldName={t('Subheader')} placeholder={t('Come up with a subtitle for your story')} - initialValue={settingsForm.subtitle} + initialValue={settingsForm.subtitle || ''} value={(value) => setSettingsForm('subtitle', value)} allowEnterKey={false} maxLength={100} From 3f7679710f0021f555dcadf7bbd0049eb0d42360 Mon Sep 17 00:00:00 2001 From: Untone Date: Sat, 17 Feb 2024 18:44:56 +0300 Subject: [PATCH 09/33] minor --- public/locales/ru/translation.json | 1 + .../Feed/ArticleCard/ArticleCard.tsx | 37 ++++++++++++------- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/public/locales/ru/translation.json b/public/locales/ru/translation.json index bcdc7e2c..716bf306 100644 --- a/public/locales/ru/translation.json +++ b/public/locales/ru/translation.json @@ -529,6 +529,7 @@ "repeat": "повторить", "resend confirmation link": "отправить ссылку ещё раз", "shout": "пост", + "shout not found": "публикация не найдена", "shoutsWithCount": "{count} {count, plural, one {пост} few {поста} other {постов}}", "sign in": "войти", "sign up or sign in": "зарегистрироваться или войти", diff --git a/src/components/Feed/ArticleCard/ArticleCard.tsx b/src/components/Feed/ArticleCard/ArticleCard.tsx index 58f69cdf..92ecb5f1 100644 --- a/src/components/Feed/ArticleCard/ArticleCard.tsx +++ b/src/components/Feed/ArticleCard/ArticleCard.tsx @@ -23,7 +23,7 @@ import stylesHeader from '../../Nav/Header/Header.module.scss' import styles from './ArticleCard.module.scss' export type ArticleCardProps = { - // TODO: refactor this, please +// TODO: refactor this, please settings?: { noicon?: boolean noimage?: boolean @@ -67,7 +67,7 @@ const getTitleAndSubtitle = ( subtitle: string } => { let title = article.title - let subtitle = article.subtitle + let subtitle: string = article.subtitle || '' if (!subtitle) { let tt = article.title?.split('. ') || [] @@ -79,7 +79,7 @@ const getTitleAndSubtitle = ( if (tt && tt.length > 1) { const sep = article.title?.replace(tt[0], '').split(' ', 1)[0] title = tt[0] + (sep === '.' || sep === ':' ? '' : sep) - subtitle = capitalize(article.title?.replace(tt[0] + sep, ''), true) + subtitle = capitalize(article.title?.replace(tt[0] + sep, ''), true) || '' } } @@ -131,6 +131,7 @@ export const ArticleCard = (props: ArticleCardProps) => { scrollTo: 'comments', }) } + return (
    { [aspectRatio()]: props.withAspectRatio, })} > + {/* Cover Image */} + {/* Cover Image Container */}
    {
    + + {/* Shout Card Content */}
    + {/* Shout Card Icon */} { + {/* Main Topic */} { /> + {/* Title and Subtitle */}
    {
    + + {/* Details */} + {/* Author and Date */}
    - {(a: Author) => { - return ( - - ) - }} + {(a: Author) => ( + + )}
    @@ -244,6 +253,8 @@ export const ArticleCard = (props: ArticleCardProps) => {
    + + {/* Description */}
    From c2035b801af2f1921427df86af80821bc872e1e7 Mon Sep 17 00:00:00 2001 From: Untone Date: Sat, 17 Feb 2024 21:57:02 +0300 Subject: [PATCH 10/33] update-fixxd --- src/components/Feed/ArticleCard/ArticleCard.tsx | 4 ++-- src/context/editor.tsx | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/components/Feed/ArticleCard/ArticleCard.tsx b/src/components/Feed/ArticleCard/ArticleCard.tsx index 92ecb5f1..760ce1e3 100644 --- a/src/components/Feed/ArticleCard/ArticleCard.tsx +++ b/src/components/Feed/ArticleCard/ArticleCard.tsx @@ -23,7 +23,7 @@ import stylesHeader from '../../Nav/Header/Header.module.scss' import styles from './ArticleCard.module.scss' export type ArticleCardProps = { -// TODO: refactor this, please + // TODO: refactor this, please settings?: { noicon?: boolean noimage?: boolean @@ -192,7 +192,7 @@ export const ArticleCard = (props: ArticleCardProps) => { diff --git a/src/context/editor.tsx b/src/context/editor.tsx index da11f919..40f21d2d 100644 --- a/src/context/editor.tsx +++ b/src/context/editor.tsx @@ -229,10 +229,15 @@ export const EditorProvider = (props: { children: JSX.Element }) => { const publishShoutById = async (shout_id: number) => { try { - const newShout = await apiClient.updateArticle({ + const { shout: newShout, error } = await apiClient.updateArticle({ shout_id, publish: true, }) + if (error) { + console.error(error) + snackbar?.showSnackbar({ type: 'error', body: error }) + return + } if (newShout) { addArticles([newShout]) openPage(router, 'feed') From 9b7079def57226fa2bedf59ac14658c80ea1cd52 Mon Sep 17 00:00:00 2001 From: Untone Date: Mon, 4 Mar 2024 18:06:40 +0300 Subject: [PATCH 11/33] naming-fix --- src/components/Feed/ArticleCard/ArticleCard.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/Feed/ArticleCard/ArticleCard.tsx b/src/components/Feed/ArticleCard/ArticleCard.tsx index 70841971..a22db42d 100644 --- a/src/components/Feed/ArticleCard/ArticleCard.tsx +++ b/src/components/Feed/ArticleCard/ArticleCard.tsx @@ -70,16 +70,16 @@ const getTitleAndSubtitle = ( let subtitle: string = article.subtitle || '' if (!subtitle) { - let tt = article.title?.split('. ') || [] + let titleParts = article.title?.split('. ') || [] - if (tt?.length === 1) { - tt = article.title?.split(/{!|\?|:|;}\s/) || [] + if (titleParts?.length === 1) { + titleParts = article.title?.split(/{!|\?|:|;}\s/) || [] } - if (tt && tt.length > 1) { - const sep = article.title?.replace(tt[0], '').split(' ', 1)[0] - title = tt[0] + (sep === '.' || sep === ':' ? '' : sep) - subtitle = capitalize(article.title?.replace(tt[0] + sep, ''), true) || '' + if (titleParts && titleParts.length > 1) { + const sep = article.title?.replace(titleParts[0], '').split(' ', 1)[0] + title = titleParts[0] + (sep === '.' || sep === ':' ? '' : sep) + subtitle = capitalize(article.title?.replace(titleParts[0] + sep, ''), true) || '' } } From f8bf3d86a0c1b61138650f7dfa1ef4d64f05012a Mon Sep 17 00:00:00 2001 From: Untone Date: Tue, 5 Mar 2024 16:01:47 +0300 Subject: [PATCH 12/33] edit-access+redirect --- src/components/Inbox/DialogAvatar.tsx | 5 ++++- src/graphql/client/core.ts | 7 +++++++ src/pages/edit.page.tsx | 12 ++++++++++-- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/components/Inbox/DialogAvatar.tsx b/src/components/Inbox/DialogAvatar.tsx index 875cdc98..1e2d68e4 100644 --- a/src/components/Inbox/DialogAvatar.tsx +++ b/src/components/Inbox/DialogAvatar.tsx @@ -51,7 +51,10 @@ const DialogAvatar = (props: Props) => { {nameFirstLetter()}
    }>
    diff --git a/src/graphql/client/core.ts b/src/graphql/client/core.ts index 753f2f86..e8a2aaf7 100644 --- a/src/graphql/client/core.ts +++ b/src/graphql/client/core.ts @@ -210,6 +210,13 @@ export const apiClient = { return resp.data.get_shout }, + getMyShout: async (shout_id: number) => { + const resp = await apiClient.private.query(shoutLoad, { shout_id }).toPromise() + if (resp.error) console.error(resp) + + return resp.data.get_shout + }, + getShouts: async (options: LoadShoutsOptions) => { const resp = await publicGraphQLClient.query(shoutsLoadBy, { options }).toPromise() if (resp.error) console.error(resp) diff --git a/src/pages/edit.page.tsx b/src/pages/edit.page.tsx index 031eedf4..f4b7fd00 100644 --- a/src/pages/edit.page.tsx +++ b/src/pages/edit.page.tsx @@ -7,8 +7,10 @@ import { useLocalize } from '../context/localize' import { apiClient } from '../graphql/client/core' import { Shout } from '../graphql/schema/core.gen' import { useRouter } from '../stores/router' +import { router } from '../stores/router' import { LayoutType } from './types' +import { redirectPage } from "@nanostores/router"; const EditView = lazy(() => import('../components/Views/EditView/EditView')) @@ -21,8 +23,14 @@ export const EditPage = () => { const [shout, setShout] = createSignal(null) onMount(async () => { - const loadedShout = await apiClient.getShoutById(shoutId()) - setShout(loadedShout) + const loadedShout = await apiClient.getMyShout(shoutId()) + console.log(loadedShout) + if (loadedShout) { + setShout(loadedShout) + } + else { + redirectPage(router, 'drafts') + } }) const title = createMemo(() => { From 4ed15f405e9ab51cae334f53c60e1cb252dca734 Mon Sep 17 00:00:00 2001 From: Untone Date: Tue, 5 Mar 2024 16:07:14 +0300 Subject: [PATCH 13/33] access+userpic-fix --- public/locales/en/translation.json | 1 + public/locales/ru/translation.json | 1 + src/components/Inbox/DialogAvatar.tsx | 13 +++++++++---- src/pages/edit.page.tsx | 8 +++++--- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 5dfd02bb..cc47cdd3 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -378,6 +378,7 @@ "There are unsaved changes in your profile settings. Are you sure you want to leave the page without saving?": "There are unsaved changes in your profile settings. Are you sure you want to leave the page without saving?", "There are unsaved changes in your publishing settings. Are you sure you want to leave the page without saving?": "There are unsaved changes in your publishing settings. Are you sure you want to leave the page without saving?", "This comment has not yet been rated": "This comment has not yet been rated", + "This content is not published yet": "This content is not published yet", "This email is": "This email is", "This email is not verified": "This email is not verified", "This email is verified": "This email is verified", diff --git a/public/locales/ru/translation.json b/public/locales/ru/translation.json index 573b313c..1c6e5596 100644 --- a/public/locales/ru/translation.json +++ b/public/locales/ru/translation.json @@ -148,6 +148,7 @@ "Enter the code or click the link from email to confirm": "Введите код из письма или пройдите по ссылке в письме для подтверждения регистрации", "Enter your new password": "Введите новый пароль", "Enter": "Войти", + "This content is not published yet": "Содержимое ещё не опубликовано", "Error": "Ошибка", "Please give us your email address": "Пожалуйста, укажите свою почту, чтобы получить ссылку для сброса пароля", "Experience": "Личный опыт", diff --git a/src/components/Inbox/DialogAvatar.tsx b/src/components/Inbox/DialogAvatar.tsx index 1e2d68e4..d34067d6 100644 --- a/src/components/Inbox/DialogAvatar.tsx +++ b/src/components/Inbox/DialogAvatar.tsx @@ -51,10 +51,15 @@ const DialogAvatar = (props: Props) => { {nameFirstLetter()}}>
    diff --git a/src/pages/edit.page.tsx b/src/pages/edit.page.tsx index f4b7fd00..c2aa7b4b 100644 --- a/src/pages/edit.page.tsx +++ b/src/pages/edit.page.tsx @@ -9,13 +9,15 @@ import { Shout } from '../graphql/schema/core.gen' import { useRouter } from '../stores/router' import { router } from '../stores/router' +import { redirectPage } from '@nanostores/router' +import { useSnackbar } from '../context/snackbar' import { LayoutType } from './types' -import { redirectPage } from "@nanostores/router"; const EditView = lazy(() => import('../components/Views/EditView/EditView')) export const EditPage = () => { const { page } = useRouter() + const snackbar = useSnackbar() const { t } = useLocalize() const shoutId = createMemo(() => Number((page().params as Record<'shoutId', string>).shoutId)) @@ -27,8 +29,8 @@ export const EditPage = () => { console.log(loadedShout) if (loadedShout) { setShout(loadedShout) - } - else { + } else { + await snackbar?.showSnackbar({ type: 'error', body: t('This content is not published yet') }) redirectPage(router, 'drafts') } }) From 2d7fd38d82d0b56df6b64abf78037ec839ccef68 Mon Sep 17 00:00:00 2001 From: Untone Date: Tue, 5 Mar 2024 16:44:51 +0300 Subject: [PATCH 14/33] get-my-shout --- src/graphql/client/core.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/graphql/client/core.ts b/src/graphql/client/core.ts index e8a2aaf7..569885bb 100644 --- a/src/graphql/client/core.ts +++ b/src/graphql/client/core.ts @@ -199,16 +199,11 @@ export const apiClient = { console.debug('[graphql.client.core] authorsLoadBy:', resp) return resp.data.load_authors_by }, + getShoutBySlug: async (slug: string) => { const resp = await publicGraphQLClient.query(shoutLoad, { slug }).toPromise() return resp.data.get_shout }, - getShoutById: async (shout_id: number) => { - const resp = await publicGraphQLClient.query(shoutLoad, { shout_id }).toPromise() - if (resp.error) console.error(resp) - - return resp.data.get_shout - }, getMyShout: async (shout_id: number) => { const resp = await apiClient.private.query(shoutLoad, { shout_id }).toPromise() From dc719120b252a53f96e6f258169834008edcb819 Mon Sep 17 00:00:00 2001 From: Untone Date: Tue, 5 Mar 2024 18:20:47 +0300 Subject: [PATCH 15/33] debug-update-shout --- src/context/editor.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/context/editor.tsx b/src/context/editor.tsx index 40f21d2d..1fe675e1 100644 --- a/src/context/editor.tsx +++ b/src/context/editor.tsx @@ -127,6 +127,10 @@ export const EditorProvider = (props: { children: JSX.Element }) => { } const updateShout = async (formToUpdate: ShoutForm, { publish }: { publish: boolean }) => { + if (!formToUpdate.shoutId) { + console.error(formToUpdate) + return {"error": "not enought data"} + } return await apiClient.updateArticle({ shout_id: formToUpdate.shoutId, shout_input: { @@ -228,6 +232,10 @@ export const EditorProvider = (props: { children: JSX.Element }) => { } const publishShoutById = async (shout_id: number) => { + if (!shout_id) { + console.error(`shout_id is ${shout_id}`) + return + } try { const { shout: newShout, error } = await apiClient.updateArticle({ shout_id, From 449154bd1bf7b2ededfdffc9e074c2e3056f5505 Mon Sep 17 00:00:00 2001 From: Untone Date: Tue, 5 Mar 2024 18:52:34 +0300 Subject: [PATCH 16/33] get-shouts-drafts --- src/graphql/client/core.ts | 2 +- src/graphql/query/core/articles-load-drafts.ts | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/graphql/client/core.ts b/src/graphql/client/core.ts index 569885bb..008915f3 100644 --- a/src/graphql/client/core.ts +++ b/src/graphql/client/core.ts @@ -177,7 +177,7 @@ export const apiClient = { getDrafts: async (): Promise => { const response = await apiClient.private.query(draftsLoad, {}).toPromise() console.debug('[graphql.client.core] getDrafts:', response) - return response.data.load_shouts_drafts + return response.data.get_shouts_drafts }, createReaction: async (input: ReactionInput) => { const response = await apiClient.private.mutation(reactionCreate, { reaction: input }).toPromise() diff --git a/src/graphql/query/core/articles-load-drafts.ts b/src/graphql/query/core/articles-load-drafts.ts index af33cf9a..ae425df4 100644 --- a/src/graphql/query/core/articles-load-drafts.ts +++ b/src/graphql/query/core/articles-load-drafts.ts @@ -2,7 +2,7 @@ import { gql } from '@urql/core' export default gql` query LoadDraftsQuery { - load_shouts_drafts { + get_shouts_drafts { id title subtitle @@ -23,6 +23,13 @@ export default gql` followers } } + created_by { + id + name + slug + pic + created_at + } authors { id name @@ -35,7 +42,6 @@ export default gql` featured_at stat { viewed - rating commented } From ce6687408992fa6e8367eccc395693a17ef0f5eb Mon Sep 17 00:00:00 2001 From: Untone Date: Wed, 6 Mar 2024 10:30:07 +0300 Subject: [PATCH 17/33] no-created-by-unwrap --- src/graphql/client/core.ts | 6 +-- src/graphql/query/core/article-my.ts | 50 +++++++++++++++++++ .../query/core/articles-load-drafts.ts | 7 --- 3 files changed, 53 insertions(+), 10 deletions(-) create mode 100644 src/graphql/query/core/article-my.ts diff --git a/src/graphql/client/core.ts b/src/graphql/client/core.ts index 008915f3..f376390e 100644 --- a/src/graphql/client/core.ts +++ b/src/graphql/client/core.ts @@ -30,6 +30,7 @@ import unfollowMutation from '../mutation/core/unfollow' import shoutLoad from '../query/core/article-load' import shoutsLoadBy from '../query/core/articles-load-by' import draftsLoad from '../query/core/articles-load-drafts' +import getMyShout from '../query/core/article-my' import myFeed from '../query/core/articles-load-feed' import loadShoutsTopRandom from '../query/core/articles-load-random-top' import articlesLoadRandomTopic from '../query/core/articles-load-random-topic' @@ -41,7 +42,6 @@ import authorFollows from '../query/core/author-follows' import authorId from '../query/core/author-id' import authorsAll from '../query/core/authors-all' import authorsLoadBy from '../query/core/authors-load-by' -import mySubscriptions from '../query/core/my-followed' import reactionsLoadBy from '../query/core/reactions-load-by' import topicBySlug from '../query/core/topic-by-slug' import topicsAll from '../query/core/topics-all' @@ -206,10 +206,10 @@ export const apiClient = { }, getMyShout: async (shout_id: number) => { - const resp = await apiClient.private.query(shoutLoad, { shout_id }).toPromise() + const resp = await apiClient.private.query(getMyShout, { shout_id }).toPromise() if (resp.error) console.error(resp) - return resp.data.get_shout + return resp.data.get_my_shout }, getShouts: async (options: LoadShoutsOptions) => { diff --git a/src/graphql/query/core/article-my.ts b/src/graphql/query/core/article-my.ts new file mode 100644 index 00000000..1399efe0 --- /dev/null +++ b/src/graphql/query/core/article-my.ts @@ -0,0 +1,50 @@ +import { gql } from '@urql/core' + +export default gql` + query GetMyShout($shout_id: Int!) { + get_my_shout(shout_id: $shout_id) { + id + title + lead + description + subtitle + slug + layout + cover + cover_caption + body + media + updated_by { + id + name + slug + pic + created_at + } + # community + main_topic + topics { + id + title + body + slug + stat { + shouts + authors + followers + } + } + authors { + id + name + slug + pic + created_at + } + created_at + updated_at + published_at + featured_at + } + } +` diff --git a/src/graphql/query/core/articles-load-drafts.ts b/src/graphql/query/core/articles-load-drafts.ts index ae425df4..f81d0a2f 100644 --- a/src/graphql/query/core/articles-load-drafts.ts +++ b/src/graphql/query/core/articles-load-drafts.ts @@ -23,13 +23,6 @@ export default gql` followers } } - created_by { - id - name - slug - pic - created_at - } authors { id name From 5b97ea374630933687ef7fac303b9500341555bf Mon Sep 17 00:00:00 2001 From: Untone Date: Wed, 6 Mar 2024 12:04:33 +0300 Subject: [PATCH 18/33] delete-reaction-fix --- src/context/editor.tsx | 2 +- src/context/reactions.tsx | 9 ++++++++- src/graphql/client/core.ts | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/context/editor.tsx b/src/context/editor.tsx index 1fe675e1..da9fe026 100644 --- a/src/context/editor.tsx +++ b/src/context/editor.tsx @@ -129,7 +129,7 @@ export const EditorProvider = (props: { children: JSX.Element }) => { const updateShout = async (formToUpdate: ShoutForm, { publish }: { publish: boolean }) => { if (!formToUpdate.shoutId) { console.error(formToUpdate) - return {"error": "not enought data"} + return { error: 'not enought data' } } return await apiClient.updateArticle({ shout_id: formToUpdate.shoutId, diff --git a/src/context/reactions.tsx b/src/context/reactions.tsx index f23c5044..a9ba2a10 100644 --- a/src/context/reactions.tsx +++ b/src/context/reactions.tsx @@ -5,6 +5,8 @@ import { createStore, reconcile } from 'solid-js/store' import { apiClient } from '../graphql/client/core' import { Reaction, ReactionBy, ReactionInput, ReactionKind } from '../graphql/schema/core.gen' +import { useLocalize } from './localize' +import { useSnackbar } from './snackbar' type ReactionsContextType = { reactionEntities: Record @@ -30,6 +32,8 @@ export function useReactions() { export const ReactionsProvider = (props: { children: JSX.Element }) => { const [reactionEntities, setReactionEntities] = createStore>({}) + const { t } = useLocalize() + const { showSnackbar } = useSnackbar() const loadReactionsBy = async ({ by, @@ -81,7 +85,10 @@ export const ReactionsProvider = (props: { children: JSX.Element }) => { const deleteReaction = async (reaction_id: number): Promise => { if (reaction_id) { - await apiClient.destroyReaction(reaction_id) + const { error } = await apiClient.destroyReaction(reaction_id) + if (error) { + await showSnackbar({ type: 'error', body: t(error) }) + } setReactionEntities({ [reaction_id]: undefined, }) diff --git a/src/graphql/client/core.ts b/src/graphql/client/core.ts index f376390e..6f42efc3 100644 --- a/src/graphql/client/core.ts +++ b/src/graphql/client/core.ts @@ -28,9 +28,9 @@ import reactionDestroy from '../mutation/core/reaction-destroy' import reactionUpdate from '../mutation/core/reaction-update' import unfollowMutation from '../mutation/core/unfollow' import shoutLoad from '../query/core/article-load' +import getMyShout from '../query/core/article-my' import shoutsLoadBy from '../query/core/articles-load-by' import draftsLoad from '../query/core/articles-load-drafts' -import getMyShout from '../query/core/article-my' import myFeed from '../query/core/articles-load-feed' import loadShoutsTopRandom from '../query/core/articles-load-random-top' import articlesLoadRandomTopic from '../query/core/articles-load-random-topic' From e2c98ded5e37a182bfaa34d2b801decf451a5990 Mon Sep 17 00:00:00 2001 From: Untone Date: Wed, 6 Mar 2024 12:49:06 +0300 Subject: [PATCH 19/33] reaction-error-handling --- src/context/reactions.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/context/reactions.tsx b/src/context/reactions.tsx index a9ba2a10..78c1e676 100644 --- a/src/context/reactions.tsx +++ b/src/context/reactions.tsx @@ -57,7 +57,8 @@ export const ReactionsProvider = (props: { children: JSX.Element }) => { } const createReaction = async (input: ReactionInput): Promise => { - const reaction = await apiClient.createReaction(input) + const {error, reaction} = await apiClient.createReaction(input) + if (error) await showSnackbar({type: 'error', body: t(error)}) if (!reaction) return const changes = { [reaction.id]: reaction, @@ -96,8 +97,9 @@ export const ReactionsProvider = (props: { children: JSX.Element }) => { } const updateReaction = async (input: ReactionInput): Promise => { - const reaction = await apiClient.updateReaction(input) - setReactionEntities(reaction.id, reaction) + const {error, reaction} = await apiClient.updateReaction(input) + if (error) await showSnackbar({type: 'error', body: t(error)}) + if (reaction) setReactionEntities(reaction.id, reaction) return reaction } From 57a9fe42a8493b9072e730ae1f1c8239b10386a6 Mon Sep 17 00:00:00 2001 From: Untone Date: Wed, 6 Mar 2024 12:56:00 +0300 Subject: [PATCH 20/33] bypass-linter --- src/context/reactions.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/context/reactions.tsx b/src/context/reactions.tsx index 78c1e676..6e0e6756 100644 --- a/src/context/reactions.tsx +++ b/src/context/reactions.tsx @@ -57,8 +57,8 @@ export const ReactionsProvider = (props: { children: JSX.Element }) => { } const createReaction = async (input: ReactionInput): Promise => { - const {error, reaction} = await apiClient.createReaction(input) - if (error) await showSnackbar({type: 'error', body: t(error)}) + const { error, reaction } = await apiClient.createReaction(input) + if (error) await showSnackbar({ type: 'error', body: t(error) }) if (!reaction) return const changes = { [reaction.id]: reaction, @@ -97,8 +97,8 @@ export const ReactionsProvider = (props: { children: JSX.Element }) => { } const updateReaction = async (input: ReactionInput): Promise => { - const {error, reaction} = await apiClient.updateReaction(input) - if (error) await showSnackbar({type: 'error', body: t(error)}) + const { error, reaction } = await apiClient.updateReaction(input) + if (error) await showSnackbar({ type: 'error', body: t(error) }) if (reaction) setReactionEntities(reaction.id, reaction) return reaction } From 136ecda3b194ab3bb26ae924d153a253d674213b Mon Sep 17 00:00:00 2001 From: Untone Date: Wed, 6 Mar 2024 13:57:39 +0300 Subject: [PATCH 21/33] topics-comments --- src/graphql/query/core/topics-all.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/graphql/query/core/topics-all.ts b/src/graphql/query/core/topics-all.ts index ecbbb188..aea60b64 100644 --- a/src/graphql/query/core/topics-all.ts +++ b/src/graphql/query/core/topics-all.ts @@ -13,6 +13,7 @@ export default gql` shouts authors followers + comments # viewed } } From e252ce464bacce526b3b35ed7c44b9e335707968 Mon Sep 17 00:00:00 2001 From: Untone Date: Wed, 6 Mar 2024 15:53:51 +0300 Subject: [PATCH 22/33] fmt --- src/components/Topic/TopicBadge/TopicBadge.tsx | 12 +++++++----- src/pages/edit.page.tsx | 8 ++++---- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/components/Topic/TopicBadge/TopicBadge.tsx b/src/components/Topic/TopicBadge/TopicBadge.tsx index f4767986..c2679b1e 100644 --- a/src/components/Topic/TopicBadge/TopicBadge.tsx +++ b/src/components/Topic/TopicBadge/TopicBadge.tsx @@ -115,15 +115,17 @@ export const TopicBadge = (props: Props) => {
    - {t('shoutsWithCount', {count: props.topic?.stat?.shouts})} - {t('authorsWithCount', {count: props.topic?.stat?.authors})} + {t('shoutsWithCount', { count: props.topic?.stat?.shouts })} + {t('authorsWithCount', { count: props.topic?.stat?.authors })} - {t('FollowersWithCount', {count: props.topic?.stat?.followers})} + {t('FollowersWithCount', { count: props.topic?.stat?.followers })} - {t('CommentsWithCount', {count: props.topic?.stat?.comments ?? 0})} + + {t('CommentsWithCount', { count: props.topic?.stat?.comments ?? 0 })} +
    - + ) } diff --git a/src/pages/edit.page.tsx b/src/pages/edit.page.tsx index c2aa7b4b..0feb07e1 100644 --- a/src/pages/edit.page.tsx +++ b/src/pages/edit.page.tsx @@ -25,13 +25,13 @@ export const EditPage = () => { const [shout, setShout] = createSignal(null) onMount(async () => { - const loadedShout = await apiClient.getMyShout(shoutId()) + const { shout: loadedShout, error } = await apiClient.getMyShout(shoutId()) console.log(loadedShout) - if (loadedShout) { - setShout(loadedShout) - } else { + if (error) { await snackbar?.showSnackbar({ type: 'error', body: t('This content is not published yet') }) redirectPage(router, 'drafts') + } else { + setShout(loadedShout) } }) From 56a83dc8ba0c2552300ed3d72ef3204904ba7741 Mon Sep 17 00:00:00 2001 From: Untone Date: Wed, 6 Mar 2024 15:55:33 +0300 Subject: [PATCH 23/33] fmt --- src/components/Topic/TopicBadge/TopicBadge.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/components/Topic/TopicBadge/TopicBadge.tsx b/src/components/Topic/TopicBadge/TopicBadge.tsx index f4767986..c2679b1e 100644 --- a/src/components/Topic/TopicBadge/TopicBadge.tsx +++ b/src/components/Topic/TopicBadge/TopicBadge.tsx @@ -115,15 +115,17 @@ export const TopicBadge = (props: Props) => {
    - {t('shoutsWithCount', {count: props.topic?.stat?.shouts})} - {t('authorsWithCount', {count: props.topic?.stat?.authors})} + {t('shoutsWithCount', { count: props.topic?.stat?.shouts })} + {t('authorsWithCount', { count: props.topic?.stat?.authors })} - {t('FollowersWithCount', {count: props.topic?.stat?.followers})} + {t('FollowersWithCount', { count: props.topic?.stat?.followers })} - {t('CommentsWithCount', {count: props.topic?.stat?.comments ?? 0})} + + {t('CommentsWithCount', { count: props.topic?.stat?.comments ?? 0 })} +
    - + ) } From af0c7fa712b54bcb5b4f9e849c807ab5c042864d Mon Sep 17 00:00:00 2001 From: Untone Date: Wed, 6 Mar 2024 16:01:46 +0300 Subject: [PATCH 24/33] get-my-shout-fix --- src/graphql/query/core/article-my.ts | 75 +++++++++++++++------------- 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/src/graphql/query/core/article-my.ts b/src/graphql/query/core/article-my.ts index 1399efe0..dcd41ea2 100644 --- a/src/graphql/query/core/article-my.ts +++ b/src/graphql/query/core/article-my.ts @@ -3,48 +3,51 @@ import { gql } from '@urql/core' export default gql` query GetMyShout($shout_id: Int!) { get_my_shout(shout_id: $shout_id) { - id - title - lead - description - subtitle - slug - layout - cover - cover_caption - body - media - updated_by { - id - name - slug - pic - created_at - } - # community - main_topic - topics { + error + shout { id title + lead + description + subtitle + slug + layout + cover + cover_caption body - slug - stat { - shouts - authors - followers + media + updated_by { + id + name + slug + pic + created_at + } + # community + main_topic + topics { + id + title + body + slug + stat { + shouts + authors + followers + } + } + authors { + id + name + slug + pic + created_at } - } - authors { - id - name - slug - pic created_at + updated_at + published_at + featured_at } - created_at - updated_at - published_at - featured_at } } ` From d7f00cd9622a303fe3bee7085ec96aec1d91c52f Mon Sep 17 00:00:00 2001 From: Untone Date: Wed, 6 Mar 2024 22:16:55 +0300 Subject: [PATCH 25/33] query-fix --- src/graphql/client/core.ts | 1 + src/graphql/query/core/article-load.ts | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/graphql/client/core.ts b/src/graphql/client/core.ts index 2da62514..a45d1345 100644 --- a/src/graphql/client/core.ts +++ b/src/graphql/client/core.ts @@ -207,6 +207,7 @@ export const apiClient = { }, getMyShout: async (shout_id: number) => { + await apiClient.private const resp = await apiClient.private.query(getMyShout, { shout_id }).toPromise() if (resp.error) console.error(resp) diff --git a/src/graphql/query/core/article-load.ts b/src/graphql/query/core/article-load.ts index f6965dcb..96605467 100644 --- a/src/graphql/query/core/article-load.ts +++ b/src/graphql/query/core/article-load.ts @@ -1,8 +1,8 @@ import { gql } from '@urql/core' export default gql` - query LoadShoutQuery($slug: String, $shout_id: Int) { - get_shout(slug: $slug, shout_id: $shout_id) { + query LoadShoutQuery($slug: String!) { + get_shout(slug: $slug) { id title lead From 41e40ada9b63a18d05e25c6b849f2895389149e5 Mon Sep 17 00:00:00 2001 From: ilya-bkv Date: Thu, 7 Mar 2024 10:20:50 +0300 Subject: [PATCH 26/33] Comment delete message --- public/locales/en/translation.json | 3 ++- public/locales/ru/translation.json | 3 ++- src/components/Article/Comment/Comment.tsx | 13 +++++++++---- src/context/reactions.tsx | 16 ++++++++-------- src/graphql/client/core.ts | 3 +-- 5 files changed, 22 insertions(+), 16 deletions(-) diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 25a18e64..b021d2ce 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -525,5 +525,6 @@ "video": "video", "view": "view", "viewsWithCount": "{count} {count, plural, one {view} other {views}}", - "yesterday": "yesterday" + "yesterday": "yesterday", + "Failed to delete comment": "Failed to delete comment" } diff --git a/public/locales/ru/translation.json b/public/locales/ru/translation.json index 5ad79f28..33643991 100644 --- a/public/locales/ru/translation.json +++ b/public/locales/ru/translation.json @@ -552,5 +552,6 @@ "video": "видео", "view": "просмотр", "viewsWithCount": "{count} {count, plural, one {просмотр} few {просмотрa} other {просмотров}}", - "yesterday": "вчера" + "yesterday": "вчера", + "Failed to delete comment": "Не удалось удалить комментарий" } diff --git a/src/components/Article/Comment/Comment.tsx b/src/components/Article/Comment/Comment.tsx index a0fee501..d66e1767 100644 --- a/src/components/Article/Comment/Comment.tsx +++ b/src/components/Article/Comment/Comment.tsx @@ -64,14 +64,19 @@ export const Comment = (props: Props) => { }) if (isConfirmed) { - await deleteReaction(props.comment.id) - // TODO: Учесть то что deleteReaction может вернуть error - if (props.onDelete) { + const { error } = await deleteReaction(props.comment.id) + const notificationType = error ? 'error' : 'success' + const notificationMessage = error + ? t('Failed to delete comment') + : t('Comment successfully deleted') + await showSnackbar({ type: notificationType, body: notificationMessage }) + + if (!error && props.onDelete) { props.onDelete(props.comment.id) } - await showSnackbar({ body: t('Comment successfully deleted') }) } } catch (error) { + await showSnackbar({ body: 'error' }) console.error('[deleteReaction]', error) } } diff --git a/src/context/reactions.tsx b/src/context/reactions.tsx index 6e0e6756..2adbe2ef 100644 --- a/src/context/reactions.tsx +++ b/src/context/reactions.tsx @@ -21,7 +21,7 @@ type ReactionsContextType = { }) => Promise createReaction: (reaction: ReactionInput) => Promise updateReaction: (reaction: ReactionInput) => Promise - deleteReaction: (id: number) => Promise + deleteReaction: (id: number) => Promise<{ error: string }> } const ReactionsContext = createContext() @@ -84,15 +84,15 @@ export const ReactionsProvider = (props: { children: JSX.Element }) => { setReactionEntities(changes) } - const deleteReaction = async (reaction_id: number): Promise => { + const deleteReaction = async (reaction_id: number): Promise<{ error: string; reaction?: string }> => { if (reaction_id) { - const { error } = await apiClient.destroyReaction(reaction_id) - if (error) { - await showSnackbar({ type: 'error', body: t(error) }) + const result = await apiClient.destroyReaction(reaction_id) + if (!result.error) { + setReactionEntities({ + [reaction_id]: undefined, + }) } - setReactionEntities({ - [reaction_id]: undefined, - }) + return result } } diff --git a/src/graphql/client/core.ts b/src/graphql/client/core.ts index a45d1345..4cbe81c2 100644 --- a/src/graphql/client/core.ts +++ b/src/graphql/client/core.ts @@ -135,7 +135,6 @@ export const apiClient = { user?: string }): Promise => { const response = await publicGraphQLClient.query(authorFollows, params).toPromise() - console.log('!!! response:', response) return response.data.get_author_follows }, @@ -188,7 +187,7 @@ export const apiClient = { destroyReaction: async (reaction_id: number) => { const response = await apiClient.private.mutation(reactionDestroy, { reaction_id }).toPromise() console.debug('[graphql.client.core] destroyReaction:', response) - return response.data.delete_reaction.reaction + return response.data.delete_reaction }, updateReaction: async (reaction: ReactionInput) => { const response = await apiClient.private.mutation(reactionUpdate, { reaction }).toPromise() From 1d64d97f9fcd8fe419c386503cfb4765e2fb8813 Mon Sep 17 00:00:00 2001 From: Ilya Y <75578537+ilya-bkv@users.noreply.github.com> Date: Thu, 7 Mar 2024 11:07:46 +0300 Subject: [PATCH 27/33] Hotfix/parse auth errors (#423) Add fixes to login form parse errors --- public/locales/en/translation.json | 9 +- public/locales/ru/translation.json | 11 +- .../CommentDate/CommentDate.module.scss | 2 - .../Nav/AuthModal/AuthModal.module.scss | 17 +-- .../Nav/AuthModal/ChangePasswordForm.tsx | 7 +- .../Nav/AuthModal/EmailConfirm.module.scss | 13 --- src/components/Nav/AuthModal/EmailConfirm.tsx | 17 +-- src/components/Nav/AuthModal/LoginForm.tsx | 106 +++++++++--------- .../PasswordField/PasswordField.module.scss | 6 +- .../AuthModal/PasswordField/PasswordField.tsx | 41 ++++--- src/components/Nav/AuthModal/RegisterForm.tsx | 41 +++---- .../Nav/AuthModal/SendEmailConfirm.tsx | 24 ++++ .../Nav/AuthModal/SendResetLinkForm.tsx | 57 +++++----- .../SocialProviders.module.scss | 0 .../{ => SocialProviders}/SocialProviders.tsx | 8 +- .../Nav/AuthModal/SocialProviders/index.ts | 1 + src/components/Nav/AuthModal/index.tsx | 2 + src/components/Nav/AuthModal/types.ts | 8 +- src/styles/app.scss | 2 +- 19 files changed, 203 insertions(+), 169 deletions(-) delete mode 100644 src/components/Nav/AuthModal/EmailConfirm.module.scss create mode 100644 src/components/Nav/AuthModal/SendEmailConfirm.tsx rename src/components/Nav/AuthModal/{ => SocialProviders}/SocialProviders.module.scss (100%) rename src/components/Nav/AuthModal/{ => SocialProviders}/SocialProviders.tsx (70%) create mode 100644 src/components/Nav/AuthModal/SocialProviders/index.ts diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 15668aca..955ceb78 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -18,6 +18,7 @@ "Add signature": "Add signature", "Add subtitle": "Add subtitle", "Add url": "Add url", + "try": "попробуйте", "Add": "Add", "Address on Discours": "Address on Discours", "Album name": "Название aльбома", @@ -144,7 +145,6 @@ "Enter your new password": "Enter your new password", "Enter": "Enter", "Error": "Error", - "Please give us your email address": "Please provide us your email address to get the password reset link", "Experience": "Experience", "FAQ": "Tips and suggestions", "Favorite topics": "Favorite topics", @@ -254,7 +254,6 @@ "Nothing here yet": "There's nothing here yet", "Nothing is here": "There is nothing here", "Notifications": "Notifications", - "Now you can enter a new password, it must contain at least 8 characters and not be the same as the previous password": "Now you can enter a new password, it must contain at least 8 characters and not be the same as the previous password", "Or paste a link to an image": "Or paste a link to an image", "Ordered list": "Ordered list", "Our regular contributor": "Our regular contributor", @@ -323,7 +322,7 @@ "Self-publishing exists thanks to the help of wonderful people from all over the world. Thank you!": "Samizdat exists thanks to the help of wonderful people from all over the world. Thank you!", "Send link again": "Send link again", "Send": "Send", - "Set the new password": "Set the new password", + "Forgot password?": "Forgot password?", "Settings": "Settings", "Share publication": "Share publication", "Share": "Share", @@ -524,5 +523,7 @@ "video": "video", "view": "view", "viewsWithCount": "{count} {count, plural, one {view} other {views}}", - "yesterday": "yesterday" + "yesterday": "yesterday", + "It's OK. Just enter your email to receive a link to change your password": "It's OK. Just enter your email to receive a link to change your password", + "Restore password": "Restore password" } diff --git a/public/locales/ru/translation.json b/public/locales/ru/translation.json index 788d31cc..74156b69 100644 --- a/public/locales/ru/translation.json +++ b/public/locales/ru/translation.json @@ -150,7 +150,6 @@ "Enter your new password": "Введите новый пароль", "Enter": "Войти", "Error": "Ошибка", - "Please give us your email address": "Пожалуйста, укажите свою почту, чтобы получить ссылку для сброса пароля", "Experience": "Личный опыт", "FAQ": "Советы и предложения", "Favorite topics": "Избранные темы", @@ -266,7 +265,6 @@ "Nothing here yet": "Здесь пока ничего нет", "Nothing is here": "Здесь ничего нет", "Notifications": "Уведомления", - "Now you can enter a new password, it must contain at least 8 characters and not be the same as the previous password": "Теперь можете ввести новый пароль, он должен содержать минимум 8 символов и не совпадать с предыдущим паролем", "Or paste a link to an image": "Или вставьте ссылку на изображение", "Ordered list": "Нумерованный список", "Our regular contributor": "Наш постоянный автор", @@ -287,7 +285,7 @@ "Pin": "Закрепить", "Platform Guide": "Гид по дискурсу", "Please check your email address": "Пожалуйста, проверьте введенный адрес почты", - "Please check your inbox! We have sent a password reset link.": "Пожалуйста, проверьте ваш адрес почты, мы отправили ссылку для сброса пароля", + "Please check your inbox! We have sent a password reset link.": "Пожалуйста, проверьте свою почту, мы отправили вам письмо со ссылкой для сброса пароля", "Please confirm your email to finish": "Подтвердите почту и действие совершится", "Please enter a name to sign your comments and publication": "Пожалуйста, введите имя, которое будет отображаться на сайте", "Please enter email": "Пожалуйста, введите почту", @@ -328,7 +326,7 @@ "Reports": "Репортажи", "Required": "Поле обязательно для заполнения", "Resend code": "Выслать подтверждение", - "Set the new password": "Задать новый пароль", + "Forgot password?": "Забыли пароль?", "Rules of the journal Discours": "Правила журнала Дискурс", "Save draft": "Сохранить черновик", "Save settings": "Сохранить настройки", @@ -403,6 +401,7 @@ "This email is": "Этот email", "This email is not verified": "Этот email не подтвержден", "This email is verified": "Этот email подтвержден", + "try": "попробуйте", "This email is registered": "Этот email уже зарегистрирован", "This functionality is currently not available, we would like to work on this issue. Use the download link.": "В данный момент этот функционал не доступен, бы работаем над этой проблемой. Воспользуйтесь загрузкой по ссылке.", "This month": "За месяц", @@ -550,5 +549,7 @@ "video": "видео", "view": "просмотр", "viewsWithCount": "{count} {count, plural, one {просмотр} few {просмотрa} other {просмотров}}", - "yesterday": "вчера" + "yesterday": "вчера", + "It's OK. Just enter your email to receive a link to change your password": "Ничего страшного. Просто укажите свою почту, чтобы получить ссылку для смены пароля", + "Restore password": "Восстановить пароль" } diff --git a/src/components/Article/CommentDate/CommentDate.module.scss b/src/components/Article/CommentDate/CommentDate.module.scss index 89d90585..50cf7d57 100644 --- a/src/components/Article/CommentDate/CommentDate.module.scss +++ b/src/components/Article/CommentDate/CommentDate.module.scss @@ -2,8 +2,6 @@ @include font-size(1.2rem); color: var(--secondary-color); - - // align-self: center; display: flex; align-items: flex-start; justify-content: flex-start; diff --git a/src/components/Nav/AuthModal/AuthModal.module.scss b/src/components/Nav/AuthModal/AuthModal.module.scss index 23bd6aff..2d4cbc98 100644 --- a/src/components/Nav/AuthModal/AuthModal.module.scss +++ b/src/components/Nav/AuthModal/AuthModal.module.scss @@ -1,5 +1,5 @@ .view { - background: #fff; + background: var(--background-color); min-height: 550px; position: relative; justify-content: center; @@ -154,17 +154,6 @@ margin-bottom: 1em; } -.authInfo { - font-weight: 400; - font-size: smaller; - margin-top: -2em; - position: absolute; - - .warn { - color: #a00; - } -} - .authForm { display: flex; flex: 1; @@ -221,3 +210,7 @@ line-height: 24px; margin-bottom: 52px; } + +.submitError { + margin: -1rem 0 -2rem; +} diff --git a/src/components/Nav/AuthModal/ChangePasswordForm.tsx b/src/components/Nav/AuthModal/ChangePasswordForm.tsx index 2fa9c3a9..a317e834 100644 --- a/src/components/Nav/AuthModal/ChangePasswordForm.tsx +++ b/src/components/Nav/AuthModal/ChangePasswordForm.tsx @@ -33,7 +33,7 @@ export const ChangePasswordForm = () => { event.preventDefault() setIsSubmitting(true) if (newPassword()) { - await changePassword(newPassword(), searchParams()?.token) + changePassword(newPassword(), searchParams()?.token) setTimeout(() => { setIsSubmitting(false) setIsSuccess(true) @@ -60,11 +60,6 @@ export const ChangePasswordForm = () => { >

    {t('Enter a new password')}

    -
    - {t( - 'Now you can enter a new password, it must contain at least 8 characters and not be the same as the previous password', - )} -
    {validationErrors().password}
    diff --git a/src/components/Nav/AuthModal/EmailConfirm.module.scss b/src/components/Nav/AuthModal/EmailConfirm.module.scss deleted file mode 100644 index 323b5384..00000000 --- a/src/components/Nav/AuthModal/EmailConfirm.module.scss +++ /dev/null @@ -1,13 +0,0 @@ -.title { - font-size: 26px; - line-height: 32px; - font-weight: 700; - color: #141414; - margin-bottom: 16px; -} - -.text { - font-size: 15px; - line-height: 24px; - margin-bottom: 52px; -} diff --git a/src/components/Nav/AuthModal/EmailConfirm.tsx b/src/components/Nav/AuthModal/EmailConfirm.tsx index 1bd88086..2a7367bc 100644 --- a/src/components/Nav/AuthModal/EmailConfirm.tsx +++ b/src/components/Nav/AuthModal/EmailConfirm.tsx @@ -17,19 +17,20 @@ export const EmailConfirm = () => { const [emailConfirmed, setEmailConfirmed] = createSignal(false) createEffect(() => { - const e = session()?.user?.email - const v = session()?.user?.email_verified - if (e) { - setEmail(e.toLowerCase()) - if (v) setEmailConfirmed(v) + const email = session()?.user?.email + const isVerified = session()?.user?.email_verified + + if (email) { + setEmail(email.toLowerCase()) + if (isVerified) setEmailConfirmed(isVerified) if (authError()) { changeSearchParams({}, true) } } - }) - createEffect(() => { - if (authError()) console.debug('[AuthModal.EmailConfirm] auth error:', authError()) + if (authError()) { + console.debug('[AuthModal.EmailConfirm] auth error:', authError()) + } }) return ( diff --git a/src/components/Nav/AuthModal/LoginForm.tsx b/src/components/Nav/AuthModal/LoginForm.tsx index b708d32b..4ea5a5f6 100644 --- a/src/components/Nav/AuthModal/LoginForm.tsx +++ b/src/components/Nav/AuthModal/LoginForm.tsx @@ -1,7 +1,7 @@ import type { AuthModalSearchParams } from './types' import { clsx } from 'clsx' -import { Show, createSignal } from 'solid-js' +import { JSX, Show, createEffect, createSignal } from 'solid-js' import { useLocalize } from '../../../context/localize' import { useSession } from '../../../context/session' @@ -27,12 +27,11 @@ type ValidationErrors = Partial> export const LoginForm = () => { const { changeSearchParams } = useRouter() const { t } = useLocalize() - const [submitError, setSubmitError] = createSignal('') + const [submitError, setSubmitError] = createSignal() const [isSubmitting, setIsSubmitting] = createSignal(false) const [password, setPassword] = createSignal('') const [validationErrors, setValidationErrors] = createSignal({}) - // TODO: better solution for interactive error messages - const [isEmailNotConfirmed, setIsEmailNotConfirmed] = createSignal(false) + const [isLinkSent, setIsLinkSent] = createSignal(false) const authFormRef: { current: HTMLFormElement } = { current: null } const { showSnackbar } = useSnackbar() @@ -52,43 +51,43 @@ export const LoginForm = () => { event.preventDefault() setIsLinkSent(true) - setIsEmailNotConfirmed(false) - setSubmitError('') - changeSearchParams({ mode: 'send-reset-link' }) - // NOTE: temporary solution, needs logic in authorizer - /* FIXME: - const { authorizer } = useSession() - const result = await authorizer().verifyEmail({ token }) - if (!result) setSubmitError('cant sign send link') - */ + setSubmitError() + changeSearchParams({ mode: 'send-confirm-email' }) } + const preSendValidate = async (value: string, type: 'email' | 'password'): Promise => { + if (type === 'email') { + if (value === '' || !validateEmail(value)) { + setValidationErrors((prev) => ({ + ...prev, + email: t('Invalid email'), + })) + return false + } + } else if (type === 'password') { + if (value === '') { + setValidationErrors((prev) => ({ + ...prev, + password: t('Please enter password'), + })) + return false + } + } + return true + } const handleSubmit = async (event: Event) => { event.preventDefault() + await preSendValidate(email(), 'email') + await preSendValidate(password(), 'password') + setIsLinkSent(false) - setIsEmailNotConfirmed(false) - setSubmitError('') - - const newValidationErrors: ValidationErrors = {} - - const validateAndSetError = (field, message) => { - if (!field()) { - newValidationErrors[field.name] = t(message) - } - } - - validateAndSetError(email, 'Please enter email') - validateAndSetError(() => validateEmail(email()), 'Invalid email') - validateAndSetError(password, 'Please enter password') - - if (Object.keys(newValidationErrors).length > 0) { - setValidationErrors(newValidationErrors) + setSubmitError() + if (Object.keys(validationErrors()).length > 0) { authFormRef.current - .querySelector(`input[name="${Object.keys(newValidationErrors)[0]}"]`) + .querySelector(`input[name="${Object.keys(validationErrors())[0]}"]`) ?.focus() - return } @@ -96,14 +95,27 @@ export const LoginForm = () => { try { const { errors } = await signIn({ email: email(), password: password() }) + console.error('[signIn errors]', errors) if (errors?.length > 0) { if (errors.some((error) => error.message.includes('bad user credentials'))) { setValidationErrors((prev) => ({ ...prev, password: t('Something went wrong, check email and password'), })) + } else if (errors.some((error) => error.message.includes('user not found'))) { + setSubmitError('Пользователь не найден') + } else if (errors.some((error) => error.message.includes('email not verified'))) { + setSubmitError( +
    + {t('This email is not verified')} + {'. '} + + {t('Send link again')} + +
    , + ) } else { - setSubmitError(t('Error')) + setSubmitError(t('Error', errors[0].message)) } return } @@ -121,19 +133,6 @@ export const LoginForm = () => { (authFormRef.current = el)}>
    - -
    -
    {submitError()}
    - - - {t('Send link again')} - - -
    -
    - -
    {t('Link sent, check your email')}
    -
    {
    - handlePasswordInput(value)} /> - -
    - {validationErrors().password} -
    + handlePasswordInput(value)} + /> + + +
    {submitError()}
    @@ -175,7 +177,7 @@ export const LoginForm = () => { }) } > - {t('Set the new password')} + {t('Forgot password?')}
    diff --git a/src/components/Nav/AuthModal/PasswordField/PasswordField.module.scss b/src/components/Nav/AuthModal/PasswordField/PasswordField.module.scss index 7faae657..f050d700 100644 --- a/src/components/Nav/AuthModal/PasswordField/PasswordField.module.scss +++ b/src/components/Nav/AuthModal/PasswordField/PasswordField.module.scss @@ -31,11 +31,11 @@ } /* Red/500 */ - color: #d00820; + color: orange; a { - color: #d00820; - border-color: #d00820; + color: orange; + border-color: orange; &:hover { color: var(--default-color-invert); diff --git a/src/components/Nav/AuthModal/PasswordField/PasswordField.tsx b/src/components/Nav/AuthModal/PasswordField/PasswordField.tsx index c2e0e3bc..bf7f00b1 100644 --- a/src/components/Nav/AuthModal/PasswordField/PasswordField.tsx +++ b/src/components/Nav/AuthModal/PasswordField/PasswordField.tsx @@ -11,21 +11,23 @@ type Props = { disabled?: boolean placeholder?: string errorMessage?: (error: string) => void + setError?: string onInput: (value: string) => void + onBlur?: (value: string) => void variant?: 'login' | 'registration' disableAutocomplete?: boolean } +const minLength = 8 +const hasNumber = /\d/ +const hasSpecial = /[!#$%&*@^]/ + export const PasswordField = (props: Props) => { const { t } = useLocalize() const [showPassword, setShowPassword] = createSignal(false) const [error, setError] = createSignal() const validatePassword = (passwordToCheck) => { - const minLength = 8 - const hasNumber = /\d/ - const hasSpecial = /[!#$%&*@^]/ - if (passwordToCheck.length < minLength) { return t('Password should be at least 8 characters') } @@ -35,11 +37,17 @@ export const PasswordField = (props: Props) => { if (!hasSpecial.test(passwordToCheck)) { return t('Password should contain at least one special character: !@#$%^&*') } - return null } - const handleInputChange = (value) => { + const handleInputBlur = (value: string) => { + if (props.variant === 'login') { + return props.onBlur(value) + } + if (value.length < 1) { + return + } + props.onInput(value) const errorValue = validatePassword(value) if (errorValue) { @@ -58,14 +66,13 @@ export const PasswordField = (props: Props) => { { defer: true }, ), ) + createEffect(() => { + setError(props.setError) + }) return (
    -
    +
    { autocomplete={props.disableAutocomplete ? 'one-time-code' : 'current-password'} type={showPassword() ? 'text' : 'password'} placeholder={props.placeholder || t('Password')} - onInput={(event) => handleInputChange(event.currentTarget.value)} + onBlur={(event) => handleInputBlur(event.currentTarget.value)} /> - -
    {error()}
    + +
    + {error()} +
    diff --git a/src/components/Nav/AuthModal/RegisterForm.tsx b/src/components/Nav/AuthModal/RegisterForm.tsx index ec78b44e..383eaad5 100644 --- a/src/components/Nav/AuthModal/RegisterForm.tsx +++ b/src/components/Nav/AuthModal/RegisterForm.tsx @@ -28,10 +28,6 @@ type FormFields = { type ValidationErrors = Partial> -const handleEmailInput = (newEmail: string) => { - setEmail(newEmail.toLowerCase()) -} - export const RegisterForm = () => { const { changeSearchParams } = useRouter() const { t } = useLocalize() @@ -52,6 +48,7 @@ export const RegisterForm = () => { } const handleSubmit = async (event: Event) => { + console.log('!!! handleSubmit:', handleSubmit) event.preventDefault() if (passwordError()) { setValidationErrors((errors) => ({ ...errors, password: passwordError() })) @@ -137,7 +134,8 @@ export const RegisterForm = () => { setValidationErrors((prev) => ({ email: ( <> - {t('This email is verified')}. {t('You can')} + {t('This email is registered')}. {t('try')} + {', '} changeSearchParams({ mode: 'login' })}> {t('enter')} @@ -172,17 +170,18 @@ export const RegisterForm = () => { } } + const handleEmailInput = (newEmail: string) => { + setEmailStatus('') + setValidationErrors({}) + setEmail(newEmail.toLowerCase()) + } + return ( <> (authFormRef.current = el)}>
    - -
    -
    {submitError()}
    -
    -
    { onBlur={handleEmailBlur} /> -
    - {validationErrors().email} -
    + +
    + {validationErrors().email} +
    +
    { -
    {t('Almost done! Check your email.')}
    -
    {t("We've sent you a message with a link to enter our website.")}
    -
    - +
    +
    {t('Almost done! Check your email.')}
    +
    {t("We've sent you a message with a link to enter our website.")}
    +
    + +
    diff --git a/src/components/Nav/AuthModal/SendEmailConfirm.tsx b/src/components/Nav/AuthModal/SendEmailConfirm.tsx new file mode 100644 index 00000000..dd8c01dd --- /dev/null +++ b/src/components/Nav/AuthModal/SendEmailConfirm.tsx @@ -0,0 +1,24 @@ +import { clsx } from 'clsx' +import { useLocalize } from '../../../context/localize' +import { hideModal } from '../../../stores/ui' + +import styles from './AuthModal.module.scss' + +export const SendEmailConfirm = () => { + const { t } = useLocalize() + return ( +
    +
    {t('Link sent, check your email')}
    +
    + +
    +
    + ) +} diff --git a/src/components/Nav/AuthModal/SendResetLinkForm.tsx b/src/components/Nav/AuthModal/SendResetLinkForm.tsx index f429a256..c8d635d7 100644 --- a/src/components/Nav/AuthModal/SendResetLinkForm.tsx +++ b/src/components/Nav/AuthModal/SendResetLinkForm.tsx @@ -79,8 +79,12 @@ export const SendResetLinkForm = () => { ref={(el) => (authFormRef.current = el)} >
    -

    {t('Set the new password')}

    -
    {t(message()) || t('Please give us your email address')}
    +

    {t('Forgot password?')}

    + +
    + {t("It's OK. Just enter your email to receive a link to change your password")} +
    +
    { class={'link'} onClick={() => changeSearchParams({ - mode: 'login', + mode: 'register', }) } > @@ -116,28 +120,31 @@ export const SendResetLinkForm = () => {
    {validationErrors().email}
    - -
    - -
    -
    - - changeSearchParams({ - mode: 'login', - }) - } - > - {t('I know the password')} - -
    + {t(message())}
    }> + <> +
    + +
    +
    + + changeSearchParams({ + mode: 'login', + }) + } + > + {t('I know the password')} + +
    + +
    ) diff --git a/src/components/Nav/AuthModal/SocialProviders.module.scss b/src/components/Nav/AuthModal/SocialProviders/SocialProviders.module.scss similarity index 100% rename from src/components/Nav/AuthModal/SocialProviders.module.scss rename to src/components/Nav/AuthModal/SocialProviders/SocialProviders.module.scss diff --git a/src/components/Nav/AuthModal/SocialProviders.tsx b/src/components/Nav/AuthModal/SocialProviders/SocialProviders.tsx similarity index 70% rename from src/components/Nav/AuthModal/SocialProviders.tsx rename to src/components/Nav/AuthModal/SocialProviders/SocialProviders.tsx index d547bb1d..297a4749 100644 --- a/src/components/Nav/AuthModal/SocialProviders.tsx +++ b/src/components/Nav/AuthModal/SocialProviders/SocialProviders.tsx @@ -1,8 +1,8 @@ import { For } from 'solid-js' -import { useLocalize } from '../../../context/localize' -import { useSession } from '../../../context/session' -import { Icon } from '../../_shared/Icon' +import { useLocalize } from '../../../../context/localize' +import { useSession } from '../../../../context/session' +import { Icon } from '../../../_shared/Icon' import styles from './SocialProviders.module.scss' @@ -18,7 +18,7 @@ export const SocialProviders = () => {
    {(provider) => ( - )} diff --git a/src/components/Nav/AuthModal/SocialProviders/index.ts b/src/components/Nav/AuthModal/SocialProviders/index.ts new file mode 100644 index 00000000..07a9863a --- /dev/null +++ b/src/components/Nav/AuthModal/SocialProviders/index.ts @@ -0,0 +1 @@ +export { SocialProviders } from './SocialProviders' diff --git a/src/components/Nav/AuthModal/index.tsx b/src/components/Nav/AuthModal/index.tsx index 760e58a0..d1df5650 100644 --- a/src/components/Nav/AuthModal/index.tsx +++ b/src/components/Nav/AuthModal/index.tsx @@ -16,12 +16,14 @@ import { RegisterForm } from './RegisterForm' import { SendResetLinkForm } from './SendResetLinkForm' import styles from './AuthModal.module.scss' +import { SendEmailConfirm } from './SendEmailConfirm' const AUTH_MODAL_MODES: Record = { login: LoginForm, register: RegisterForm, 'send-reset-link': SendResetLinkForm, 'confirm-email': EmailConfirm, + 'send-confirm-email': SendEmailConfirm, 'change-password': ChangePasswordForm, } diff --git a/src/components/Nav/AuthModal/types.ts b/src/components/Nav/AuthModal/types.ts index e2db3e78..185b5841 100644 --- a/src/components/Nav/AuthModal/types.ts +++ b/src/components/Nav/AuthModal/types.ts @@ -1,4 +1,10 @@ -export type AuthModalMode = 'login' | 'register' | 'confirm-email' | 'send-reset-link' | 'change-password' +export type AuthModalMode = + | 'login' + | 'register' + | 'confirm-email' + | 'send-confirm-email' + | 'send-reset-link' + | 'change-password' export type AuthModalSource = | 'discussions' | 'vote' diff --git a/src/styles/app.scss b/src/styles/app.scss index d563840e..db3a68bc 100644 --- a/src/styles/app.scss +++ b/src/styles/app.scss @@ -464,7 +464,7 @@ form { } .form-message--error { - color: #d00820; + color: var(--danger-color) !important; } select { From a59ee6260cffb8ed8277f76a243c1d440a261352 Mon Sep 17 00:00:00 2001 From: Untone Date: Thu, 7 Mar 2024 13:03:19 +0300 Subject: [PATCH 28/33] create-fx-fix --- src/pages/edit.page.tsx | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/pages/edit.page.tsx b/src/pages/edit.page.tsx index 0feb07e1..8ed436a5 100644 --- a/src/pages/edit.page.tsx +++ b/src/pages/edit.page.tsx @@ -1,4 +1,4 @@ -import { Show, Suspense, createMemo, createSignal, lazy, onMount } from 'solid-js' +import { Show, Suspense, createEffect, createMemo, createSignal, lazy, on, onMount } from 'solid-js' import { AuthGuard } from '../components/AuthGuard' import { Loading } from '../components/_shared/Loading' @@ -24,16 +24,18 @@ export const EditPage = () => { const [shout, setShout] = createSignal(null) - onMount(async () => { - const { shout: loadedShout, error } = await apiClient.getMyShout(shoutId()) - console.log(loadedShout) - if (error) { - await snackbar?.showSnackbar({ type: 'error', body: t('This content is not published yet') }) - redirectPage(router, 'drafts') - } else { - setShout(loadedShout) - } - }) + createEffect( + on(shoutId, async (shout_id) => { + const { shout: loadedShout, error } = await apiClient.getMyShout(shout_id) + console.log(loadedShout) + if (error) { + await snackbar?.showSnackbar({ type: 'error', body: t('This content is not published yet') }) + redirectPage(router, 'drafts') + } else { + setShout(loadedShout) + } + }), + ) const title = createMemo(() => { if (!shout()) { From 0b905eb635c3b9da2ba5cf661260782a860bce0f Mon Sep 17 00:00:00 2001 From: Untone Date: Thu, 7 Mar 2024 13:14:22 +0300 Subject: [PATCH 29/33] merged-2 --- src/pages/edit.page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/edit.page.tsx b/src/pages/edit.page.tsx index 8ed436a5..bd2a8666 100644 --- a/src/pages/edit.page.tsx +++ b/src/pages/edit.page.tsx @@ -34,7 +34,7 @@ export const EditPage = () => { } else { setShout(loadedShout) } - }), + }, { defer: true }), ) const title = createMemo(() => { From db7825fab82647a71e24b790ecc5c8154d36432c Mon Sep 17 00:00:00 2001 From: Untone Date: Thu, 7 Mar 2024 13:58:12 +0300 Subject: [PATCH 30/33] onmount-fix --- src/pages/edit.page.tsx | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/pages/edit.page.tsx b/src/pages/edit.page.tsx index bd2a8666..551e5254 100644 --- a/src/pages/edit.page.tsx +++ b/src/pages/edit.page.tsx @@ -1,4 +1,4 @@ -import { Show, Suspense, createEffect, createMemo, createSignal, lazy, on, onMount } from 'solid-js' +import { Show, Suspense, createMemo, createSignal, lazy, onMount } from 'solid-js' import { AuthGuard } from '../components/AuthGuard' import { Loading } from '../components/_shared/Loading' @@ -20,12 +20,9 @@ export const EditPage = () => { const snackbar = useSnackbar() const { t } = useLocalize() - const shoutId = createMemo(() => Number((page().params as Record<'shoutId', string>).shoutId)) - const [shout, setShout] = createSignal(null) - - createEffect( - on(shoutId, async (shout_id) => { + const loadMyShout = async (shout_id: number) => { + if (shout_id) { const { shout: loadedShout, error } = await apiClient.getMyShout(shout_id) console.log(loadedShout) if (error) { @@ -34,8 +31,19 @@ export const EditPage = () => { } else { setShout(loadedShout) } - }, { defer: true }), - ) + } + } + + onMount(async () => { + const shout_id = window.location.pathname.split('/').pop() + if (shout_id) { + try { + await loadMyShout(parseInt(shout_id, 10)) + } catch(e) { + console.error(e) + } + } + }) const title = createMemo(() => { if (!shout()) { From 2280a776b36fb2f8a7aaf1dee5d4627c1ec81d8f Mon Sep 17 00:00:00 2001 From: ilya-bkv Date: Thu, 7 Mar 2024 15:13:52 +0300 Subject: [PATCH 31/33] Improve draft saving process in EditView --- src/components/Views/EditView/EditView.tsx | 6 ++++-- src/context/editor.tsx | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/Views/EditView/EditView.tsx b/src/components/Views/EditView/EditView.tsx index 1969e338..3ef489ca 100644 --- a/src/components/Views/EditView/EditView.tsx +++ b/src/components/Views/EditView/EditView.tsx @@ -64,10 +64,11 @@ export const EditView = (props: Props) => { getDraftFromLocalStorage, } = useEditorContext() const shoutTopics = props.shout.topics || [] - const draft = getDraftFromLocalStorage(props.shout.id) + // TODO: проверить сохранение черновика в local storage (не работает) + const draft = getDraftFromLocalStorage(props.shout.id) if (draft) { - setForm(draft) + setForm(Object.keys(draft).length !== 0 ? draft : { shoutId: props.shout.id }); } else { setForm({ slug: props.shout.slug, @@ -179,6 +180,7 @@ export const EditView = (props: Props) => { let autoSaveTimeOutId: number | string | NodeJS.Timeout + //TODO: add throttle const autoSaveRecursive = () => { autoSaveTimeOutId = setTimeout(async () => { const hasChanges = !deepEqual(form, prevForm) diff --git a/src/context/editor.tsx b/src/context/editor.tsx index da9fe026..5369df88 100644 --- a/src/context/editor.tsx +++ b/src/context/editor.tsx @@ -184,8 +184,10 @@ export const EditorProvider = (props: { children: JSX.Element }) => { } const saveDraft = async (draftForm: ShoutForm) => { + console.log("!!! draftForm:", draftForm); const { error } = await updateShout(draftForm, { publish: false }) if (error) { + console.log("!!! error:", error); snackbar?.showSnackbar({ type: 'error', body: localize?.t(error) || '' }) return } From 248d06decd41b8f1b81fe22028a95bc8db95b74b Mon Sep 17 00:00:00 2001 From: ilya-bkv Date: Thu, 7 Mar 2024 15:14:58 +0300 Subject: [PATCH 32/33] cleanup code --- src/components/Nav/AuthModal/RegisterForm.tsx | 1 - src/context/editor.tsx | 2 -- 2 files changed, 3 deletions(-) diff --git a/src/components/Nav/AuthModal/RegisterForm.tsx b/src/components/Nav/AuthModal/RegisterForm.tsx index 48744474..56f272ac 100644 --- a/src/components/Nav/AuthModal/RegisterForm.tsx +++ b/src/components/Nav/AuthModal/RegisterForm.tsx @@ -48,7 +48,6 @@ export const RegisterForm = () => { } const handleSubmit = async (event: Event) => { - console.log('!!! handleSubmit:', handleSubmit) event.preventDefault() if (passwordError()) { setValidationErrors((errors) => ({ ...errors, password: passwordError() })) diff --git a/src/context/editor.tsx b/src/context/editor.tsx index 5369df88..da9fe026 100644 --- a/src/context/editor.tsx +++ b/src/context/editor.tsx @@ -184,10 +184,8 @@ export const EditorProvider = (props: { children: JSX.Element }) => { } const saveDraft = async (draftForm: ShoutForm) => { - console.log("!!! draftForm:", draftForm); const { error } = await updateShout(draftForm, { publish: false }) if (error) { - console.log("!!! error:", error); snackbar?.showSnackbar({ type: 'error', body: localize?.t(error) || '' }) return } From b31d0deed419b3a09d34521ee247a3b899520c90 Mon Sep 17 00:00:00 2001 From: Untone Date: Thu, 7 Mar 2024 15:31:09 +0300 Subject: [PATCH 33/33] merged --- .gitignore | 1 + src/components/Views/EditView/EditView.tsx | 2 +- src/pages/edit.page.tsx | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 60d6161e..6dca0a65 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ bun.lockb /blob-report/ /playwright/.cache/ /plawright-report/ +target \ No newline at end of file diff --git a/src/components/Views/EditView/EditView.tsx b/src/components/Views/EditView/EditView.tsx index 3ef489ca..83da483d 100644 --- a/src/components/Views/EditView/EditView.tsx +++ b/src/components/Views/EditView/EditView.tsx @@ -68,7 +68,7 @@ export const EditView = (props: Props) => { // TODO: проверить сохранение черновика в local storage (не работает) const draft = getDraftFromLocalStorage(props.shout.id) if (draft) { - setForm(Object.keys(draft).length !== 0 ? draft : { shoutId: props.shout.id }); + setForm(Object.keys(draft).length !== 0 ? draft : { shoutId: props.shout.id }) } else { setForm({ slug: props.shout.slug, diff --git a/src/pages/edit.page.tsx b/src/pages/edit.page.tsx index 551e5254..ef33f712 100644 --- a/src/pages/edit.page.tsx +++ b/src/pages/edit.page.tsx @@ -39,7 +39,7 @@ export const EditPage = () => { if (shout_id) { try { await loadMyShout(parseInt(shout_id, 10)) - } catch(e) { + } catch (e) { console.error(e) } }