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/public/locales/en/translation.json b/public/locales/en/translation.json index 955ceb78..7c097558 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", @@ -524,6 +525,7 @@ "view": "view", "viewsWithCount": "{count} {count, plural, one {view} other {views}}", "yesterday": "yesterday", + "Failed to delete comment": "Failed to delete comment", "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 74156b69..6dfe2bbd 100644 --- a/public/locales/ru/translation.json +++ b/public/locales/ru/translation.json @@ -149,6 +149,7 @@ "Enter the code or click the link from email to confirm": "Введите код из письма или пройдите по ссылке в письме для подтверждения регистрации", "Enter your new password": "Введите новый пароль", "Enter": "Войти", + "This content is not published yet": "Содержимое ещё не опубликовано", "Error": "Ошибка", "Experience": "Личный опыт", "FAQ": "Советы и предложения", @@ -529,6 +530,7 @@ "repeat": "повторить", "resend confirmation link": "отправить ссылку ещё раз", "shout": "пост", + "shout not found": "публикация не найдена", "shoutsWithCount": "{count} {count, plural, one {пост} few {поста} other {постов}}", "sign in": "войти", "sign up or sign in": "зарегистрироваться или войти", @@ -550,6 +552,7 @@ "view": "просмотр", "viewsWithCount": "{count} {count, plural, one {просмотр} few {просмотрa} other {просмотров}}", "yesterday": "вчера", + "Failed to delete comment": "Не удалось удалить комментарий", "It's OK. Just enter your email to receive a link to change your password": "Ничего страшного. Просто укажите свою почту, чтобы получить ссылку для смены пароля", "Restore password": "Восстановить пароль" } 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/components/Article/FullArticle.tsx b/src/components/Article/FullArticle.tsx index 1bb30930..f855f170 100644 --- a/src/components/Article/FullArticle.tsx +++ b/src/components/Article/FullArticle.tsx @@ -141,7 +141,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/components/Feed/ArticleCard/ArticleCard.tsx b/src/components/Feed/ArticleCard/ArticleCard.tsx index c181cc27..a22db42d 100644 --- a/src/components/Feed/ArticleCard/ArticleCard.tsx +++ b/src/components/Feed/ArticleCard/ArticleCard.tsx @@ -67,19 +67,19 @@ const getTitleAndSubtitle = ( subtitle: string } => { let title = article.title - let subtitle = article.subtitle + 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) || '' } } @@ -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 = createMemo( @@ -135,6 +135,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) => ( + + )}
@@ -248,6 +257,8 @@ export const ArticleCard = (props: ArticleCardProps) => {
+ + {/* Description */}
diff --git a/src/components/Inbox/DialogAvatar.tsx b/src/components/Inbox/DialogAvatar.tsx index 875cdc98..d34067d6 100644 --- a/src/components/Inbox/DialogAvatar.tsx +++ b/src/components/Inbox/DialogAvatar.tsx @@ -51,7 +51,15 @@ const DialogAvatar = (props: Props) => { {nameFirstLetter()}}>
diff --git a/src/components/Nav/AuthModal/RegisterForm.tsx b/src/components/Nav/AuthModal/RegisterForm.tsx index 383eaad5..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() })) @@ -148,9 +147,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')} ), @@ -193,7 +193,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)} /> @@ -226,8 +226,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 c8d635d7..e872cbeb 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/components/Views/EditView/EditView.tsx b/src/components/Views/EditView/EditView.tsx index 4e862696..83da483d 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) @@ -307,10 +309,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 7bc52d56..47a2dee4 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: [], } }) @@ -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} diff --git a/src/context/connect.tsx b/src/context/connect.tsx index d10d0695..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 diff --git a/src/context/editor.tsx b/src/context/editor.tsx index 5df99775..da9fe026 100644 --- a/src/context/editor.tsx +++ b/src/context/editor.tsx @@ -39,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 @@ -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) => { @@ -80,13 +80,19 @@ const removeDraftFromLocalStorage = (shoutId: number) => { } export const EditorProvider = (props: { children: JSX.Element }) => { - const { t } = useLocalize() + const localize = useLocalize() const { page } = useRouter() - 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, @@ -95,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 } @@ -110,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 } @@ -118,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: { @@ -143,48 +156,61 @@ 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 } 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) { + if (shout?.published_at) { openPage(router, 'article', { slug: shout.slug }) } else { openPage(router, 'drafts') } } catch (error) { console.error('[saveShout]', error) - showSnackbar({ type: 'error', body: t('Error') }) + snackbar?.showSnackbar({ type: 'error', body: localize?.t('Error') || '' }) } } 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 } @@ -193,20 +219,33 @@ 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) - showSnackbar({ type: 'error', body: t('Error') }) + snackbar?.showSnackbar({ type: 'error', body: localize?.t('Error') || '' }) } } const publishShoutById = async (shout_id: number) => { + if (!shout_id) { + console.error(`shout_id is ${shout_id}`) + return + } 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') @@ -215,7 +254,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') }) } } @@ -226,7 +265,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 } } diff --git a/src/context/reactions.tsx b/src/context/reactions.tsx index f23c5044..2adbe2ef 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 @@ -19,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() @@ -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, @@ -53,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, @@ -79,18 +84,22 @@ 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) { - await apiClient.destroyReaction(reaction_id) - setReactionEntities({ - [reaction_id]: undefined, - }) + const result = await apiClient.destroyReaction(reaction_id) + if (!result.error) { + setReactionEntities({ + [reaction_id]: undefined, + }) + } + return result } } 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 } diff --git a/src/graphql/client/core.ts b/src/graphql/client/core.ts index bfc0e03f..4cbe81c2 100644 --- a/src/graphql/client/core.ts +++ b/src/graphql/client/core.ts @@ -28,6 +28,7 @@ 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 myFeed from '../query/core/articles-load-feed' @@ -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' @@ -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 }, @@ -162,12 +161,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 => { @@ -178,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() @@ -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() @@ -200,15 +199,18 @@ 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() + + getMyShout: async (shout_id: number) => { + await apiClient.private + 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-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 diff --git a/src/graphql/query/core/article-my.ts b/src/graphql/query/core/article-my.ts new file mode 100644 index 00000000..dcd41ea2 --- /dev/null +++ b/src/graphql/query/core/article-my.ts @@ -0,0 +1,53 @@ +import { gql } from '@urql/core' + +export default gql` + query GetMyShout($shout_id: Int!) { + get_my_shout(shout_id: $shout_id) { + error + shout { + 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 af33cf9a..f81d0a2f 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 @@ -35,7 +35,6 @@ export default gql` featured_at stat { viewed - rating commented } 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 } } diff --git a/src/pages/edit.page.tsx b/src/pages/edit.page.tsx index 031eedf4..ef33f712 100644 --- a/src/pages/edit.page.tsx +++ b/src/pages/edit.page.tsx @@ -7,22 +7,42 @@ 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 { redirectPage } from '@nanostores/router' +import { useSnackbar } from '../context/snackbar' import { LayoutType } from './types' 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)) - const [shout, setShout] = createSignal(null) + const loadMyShout = async (shout_id: number) => { + if (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) + } + } + } onMount(async () => { - const loadedShout = await apiClient.getShoutById(shoutId()) - setShout(loadedShout) + 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(() => { 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 = (