Merge branch 'dev' of github.com:Discours/discoursio-webapp into feature/rating

This commit is contained in:
Untone 2024-05-06 23:22:29 +03:00
commit 7d515c4fe2
25 changed files with 141 additions and 139 deletions

View File

@ -13,7 +13,7 @@ jobs:
run: npm ci run: npm ci
- name: Check types - name: Check types
run: npm run typecheck run: npm run check:types
- name: Lint with Biome - name: Lint with Biome
run: npm run check:code run: npm run check:code

View File

@ -24,8 +24,8 @@
"lint:styles:fix": "stylelint **/*.{scss,css} --fix", "lint:styles:fix": "stylelint **/*.{scss,css} --fix",
"preview": "vite preview", "preview": "vite preview",
"start": "vite", "start": "vite",
"typecheck": "tsc --noEmit", "check:types": "tsc --noEmit",
"typecheck:watch": "tsc --noEmit --watch" "check:types:watch": "tsc --noEmit --watch"
}, },
"dependencies": { "dependencies": {
"form-data": "4.0.0", "form-data": "4.0.0",

View File

@ -449,6 +449,7 @@
"Write your colleagues name or email": "Write your colleague's name or email", "Write your colleagues name or email": "Write your colleague's name or email",
"You can download multiple tracks at once in .mp3, .wav or .flac formats": "You can download multiple tracks at once in .mp3, .wav or .flac formats", "You can download multiple tracks at once in .mp3, .wav or .flac formats": "You can download multiple tracks at once in .mp3, .wav or .flac formats",
"You can now login using your new password": "Теперь вы можете входить с помощью нового пароля", "You can now login using your new password": "Теперь вы можете входить с помощью нового пароля",
"You can't edit this post": "You can't edit this post",
"You were successfully authorized": "You were successfully authorized", "You were successfully authorized": "You were successfully authorized",
"You ll be able to participate in discussions, rate others' comments and learn about new responses": "You ll be able to participate in discussions, rate others' comments and learn about new responses", "You ll be able to participate in discussions, rate others' comments and learn about new responses": "You ll be able to participate in discussions, rate others' comments and learn about new responses",
"You've confirmed email": "You've confirmed email", "You've confirmed email": "You've confirmed email",

View File

@ -472,6 +472,7 @@
"You can download multiple tracks at once in .mp3, .wav or .flac formats": "Можно загрузить сразу несколько треков в форматах .mp3, .wav или .flac", "You can download multiple tracks at once in .mp3, .wav or .flac formats": "Можно загрузить сразу несколько треков в форматах .mp3, .wav или .flac",
"You can now login using your new password": "Теперь вы можете входить с помощью нового пароля", "You can now login using your new password": "Теперь вы можете входить с помощью нового пароля",
"You was successfully authorized": "Вы были успешно авторизованы", "You was successfully authorized": "Вы были успешно авторизованы",
"You can't edit this post": "Вы не можете редактировать этот материал",
"You ll be able to participate in discussions, rate others' comments and learn about new responses": "Вы сможете участвовать в обсуждениях, оценивать комментарии других и узнавать о новых ответах", "You ll be able to participate in discussions, rate others' comments and learn about new responses": "Вы сможете участвовать в обсуждениях, оценивать комментарии других и узнавать о новых ответах",
"You've confirmed email": "Вы подтвердили почту", "You've confirmed email": "Вы подтвердили почту",
"You've reached a non-existed page": "Вы попали на несуществующую страницу", "You've reached a non-existed page": "Вы попали на несуществующую страницу",

View File

@ -29,7 +29,7 @@ export const CommentsTree = (props: Props) => {
const [newReactions, setNewReactions] = createSignal<Reaction[]>([]) const [newReactions, setNewReactions] = createSignal<Reaction[]>([])
const [clearEditor, setClearEditor] = createSignal(false) const [clearEditor, setClearEditor] = createSignal(false)
const [clickedReplyId, setClickedReplyId] = createSignal<number>() const [clickedReplyId, setClickedReplyId] = createSignal<number>()
const { reactionEntities, createReaction } = useReactions() const { reactionEntities, createReaction, loadReactionsBy } = useReactions()
const comments = createMemo(() => const comments = createMemo(() =>
Object.values(reactionEntities).filter((reaction) => reaction.kind === 'COMMENT'), Object.values(reactionEntities).filter((reaction) => reaction.kind === 'COMMENT'),
@ -68,7 +68,9 @@ export const CommentsTree = (props: Props) => {
setCookie() setCookie()
} }
}) })
const [posting, setPosting] = createSignal(false)
const handleSubmitComment = async (value: string) => { const handleSubmitComment = async (value: string) => {
setPosting(true)
try { try {
await createReaction({ await createReaction({
kind: ReactionKind.Comment, kind: ReactionKind.Comment,
@ -76,10 +78,12 @@ export const CommentsTree = (props: Props) => {
shout: props.shoutId, shout: props.shoutId,
}) })
setClearEditor(true) setClearEditor(true)
await loadReactionsBy({ by: { shout: props.shoutSlug } })
} catch (error) { } catch (error) {
console.error('[handleCreate reaction]:', error) console.error('[handleCreate reaction]:', error)
} }
setClearEditor(false) setClearEditor(false)
setPosting(false)
} }
return ( return (
@ -157,6 +161,7 @@ export const CommentsTree = (props: Props) => {
placeholder={t('Write a comment...')} placeholder={t('Write a comment...')}
onSubmit={(value) => handleSubmitComment(value)} onSubmit={(value) => handleSubmitComment(value)}
setClear={clearEditor()} setClear={clearEditor()}
isPosting={posting()}
/> />
</ShowIfAuthenticated> </ShowIfAuthenticated>
</> </>

View File

@ -151,7 +151,7 @@ export const Editor = (props: Props) => {
} }
showSnackbar({ body: t('Uploading image') }) showSnackbar({ body: t('Uploading image') })
const result = await handleImageUpload(uplFile) const result = await handleImageUpload(uplFile, session()?.access_token)
editor() editor()
.chain() .chain()

View File

@ -36,6 +36,7 @@ import { UploadModalContent } from './UploadModalContent'
import { Figcaption } from './extensions/Figcaption' import { Figcaption } from './extensions/Figcaption'
import { Figure } from './extensions/Figure' import { Figure } from './extensions/Figure'
import { Loading } from '../_shared/Loading'
import styles from './SimplifiedEditor.module.scss' import styles from './SimplifiedEditor.module.scss'
type Props = { type Props = {
@ -58,6 +59,7 @@ type Props = {
controlsAlwaysVisible?: boolean controlsAlwaysVisible?: boolean
autoFocus?: boolean autoFocus?: boolean
isCancelButtonVisible?: boolean isCancelButtonVisible?: boolean
isPosting?: boolean
} }
const DEFAULT_MAX_LENGTH = 400 const DEFAULT_MAX_LENGTH = 400
@ -365,12 +367,14 @@ const SimplifiedEditor = (props: Props) => {
<Show when={isCancelButtonVisible()}> <Show when={isCancelButtonVisible()}>
<Button value={t('Cancel')} variant="secondary" onClick={handleClear} /> <Button value={t('Cancel')} variant="secondary" onClick={handleClear} />
</Show> </Show>
<Show when={!props.isPosting} fallback={<Loading />}>
<Button <Button
value={props.submitButtonText ?? t('Send')} value={props.submitButtonText ?? t('Send')}
variant="primary" variant="primary"
disabled={isEmpty()} disabled={isEmpty()}
onClick={() => props.onSubmit(html())} onClick={() => props.onSubmit(html())}
/> />
</Show>
</div> </div>
</Show> </Show>
</div> </div>

View File

@ -12,6 +12,7 @@ import { Icon } from '../../_shared/Icon'
import { Loading } from '../../_shared/Loading' import { Loading } from '../../_shared/Loading'
import { InlineForm } from '../InlineForm' import { InlineForm } from '../InlineForm'
import { useSession } from '../../../context/session'
import styles from './UploadModalContent.module.scss' import styles from './UploadModalContent.module.scss'
type Props = { type Props = {
@ -24,12 +25,12 @@ export const UploadModalContent = (props: Props) => {
const [uploadError, setUploadError] = createSignal<string | undefined>() const [uploadError, setUploadError] = createSignal<string | undefined>()
const [dragActive, setDragActive] = createSignal(false) const [dragActive, setDragActive] = createSignal(false)
const [dragError, setDragError] = createSignal<string | undefined>() const [dragError, setDragError] = createSignal<string | undefined>()
const { session } = useSession()
const { selectFiles } = createFileUploader({ multiple: false, accept: 'image/*' }) const { selectFiles } = createFileUploader({ multiple: false, accept: 'image/*' })
const runUpload = async (file: UploadFile) => { const runUpload = async (file: UploadFile) => {
try { try {
setIsUploading(true) setIsUploading(true)
const result = await handleImageUpload(file) const result = await handleImageUpload(file, session()?.access_token)
props.onClose(result) props.onClose(result)
setIsUploading(false) setIsUploading(false)
} catch (error) { } catch (error) {

View File

@ -97,7 +97,12 @@ export const LoginForm = () => {
const { errors } = await signIn({ email: email(), password: password() }) const { errors } = await signIn({ email: email(), password: password() })
console.error('[signIn errors]', errors) console.error('[signIn errors]', errors)
if (errors?.length > 0) { if (errors?.length > 0) {
if (errors.some((error) => error.message.includes('bad user credentials'))) { if (
errors.some(
(error) =>
error.message.includes('bad user credentials') || error.message.includes('user not found'),
)
) {
setValidationErrors((prev) => ({ setValidationErrors((prev) => ({
...prev, ...prev,
password: t('Something went wrong, check email and password'), password: t('Something went wrong, check email and password'),

View File

@ -61,7 +61,12 @@ export const SendResetLinkForm = () => {
redirect_uri: window.location.origin, redirect_uri: window.location.origin,
}) })
console.debug('[SendResetLinkForm] authorizer response:', data) console.debug('[SendResetLinkForm] authorizer response:', data)
if (errors?.some((error) => error.message.includes('bad user credentials'))) { if (
errors?.some(
(error) =>
error.message.includes('bad user credentials') || error.message.includes('user not found'),
)
) {
setIsUserNotFound(true) setIsUserNotFound(true)
} }
if (data.message) setMessage(data.message) if (data.message) setMessage(data.message)

View File

@ -59,6 +59,7 @@ export const Header = (props: Props) => {
const [isTopicsVisible, setIsTopicsVisible] = createSignal(false) const [isTopicsVisible, setIsTopicsVisible] = createSignal(false)
const [isZineVisible, setIsZineVisible] = createSignal(false) const [isZineVisible, setIsZineVisible] = createSignal(false)
const [isFeedVisible, setIsFeedVisible] = createSignal(false) const [isFeedVisible, setIsFeedVisible] = createSignal(false)
const { session } = useSession()
const toggleFixed = () => setFixed(!fixed()) const toggleFixed = () => setFixed(!fixed())
@ -68,7 +69,9 @@ export const Header = (props: Props) => {
let windowScrollTop = 0 let windowScrollTop = 0
createEffect(() => { createEffect(() => {
if (topics()?.length) {
setRandomTopics(getRandomTopicsFromArray(topics())) setRandomTopics(getRandomTopicsFromArray(topics()))
}
}) })
createEffect(() => { createEffect(() => {
@ -330,7 +333,11 @@ export const Header = (props: Props) => {
</div> </div>
<HeaderAuth setIsProfilePopupVisible={setIsProfilePopupVisible} /> <HeaderAuth setIsProfilePopupVisible={setIsProfilePopupVisible} />
<Show when={props.title}> <Show when={props.title}>
<div class={clsx(styles.articleControls, 'col-auto')}> <div
class={clsx(styles.articleControls, 'col-auto', {
// FIXME: use or remove [styles.articleControlsAuthorized]: session()?.user?.id,
})}
>
<SharePopup <SharePopup
title={props.title} title={props.title}
imageUrl={props.cover} imageUrl={props.cover}

View File

@ -34,12 +34,12 @@ export const HeaderAuth = (props: Props) => {
const { page } = useRouter() const { page } = useRouter()
const { session, author, isSessionLoaded } = useSession() const { session, author, isSessionLoaded } = useSession()
const { unreadNotificationsCount, showNotificationsPanel } = useNotifications() const { unreadNotificationsCount, showNotificationsPanel } = useNotifications()
const { form, toggleEditorPanel, saveShout, saveDraft, publishShout } = useEditorContext() const { form, toggleEditorPanel, saveShout, publishShout } = useEditorContext()
const handleBellIconClick = (event: Event) => { const handleBellIconClick = (event: Event) => {
event.preventDefault() event.preventDefault()
if (!author()?.id) { if (!session()?.access_token) {
showModal('auth') showModal('auth')
return return
} }
@ -48,22 +48,19 @@ export const HeaderAuth = (props: Props) => {
} }
const isEditorPage = createMemo(() => page().route === 'edit' || page().route === 'editSettings') const isEditorPage = createMemo(() => page().route === 'edit' || page().route === 'editSettings')
const isNotificationsVisible = createMemo(() => author()?.id && !isEditorPage()) const isNotificationsVisible = createMemo(() => session()?.access_token && !isEditorPage())
const isSaveButtonVisible = createMemo(() => author()?.id && isEditorPage()) const isSaveButtonVisible = createMemo(() => session()?.access_token && isEditorPage())
const isCreatePostButtonVisible = createMemo(() => !isEditorPage()) const isCreatePostButtonVisible = createMemo(() => !isEditorPage())
const isAuthenticatedControlsVisible = createMemo(() => author()?.id && session()?.user?.email_verified) const isAuthenticatedControlsVisible = createMemo(
() => session()?.access_token && session()?.user?.email_verified,
)
const handleBurgerButtonClick = () => { const handleBurgerButtonClick = () => {
toggleEditorPanel() toggleEditorPanel()
} }
// FIXME: use handler
const _handleSaveClick = () => { const handleSaveButtonClick = () => {
const hasTopics = form.selectedTopics?.length > 0
if (hasTopics) {
saveShout(form) saveShout(form)
} else {
saveDraft(form)
}
} }
const [width, setWidth] = createSignal(0) const [width, setWidth] = createSignal(0)
@ -109,14 +106,8 @@ export const HeaderAuth = (props: Props) => {
<Show when={isSessionLoaded()} keyed={true}> <Show when={isSessionLoaded()} keyed={true}>
<div class={clsx('col-auto col-lg-7', styles.usernav)}> <div class={clsx('col-auto col-lg-7', styles.usernav)}>
<div class={styles.userControl}> <div class={styles.userControl}>
<Show when={isCreatePostButtonVisible() && author()?.id}> <Show when={isCreatePostButtonVisible() && session()?.access_token}>
<div <div class={clsx(styles.userControlItem, styles.userControlItemVerbose)}>
class={clsx(
styles.userControlItem,
styles.userControlItemVerbose,
// styles.userControlItemCreate,
)}
>
<a href={getPagePath(router, 'create')}> <a href={getPagePath(router, 'create')}>
<span class={styles.textLabel}>{t('Create post')}</span> <span class={styles.textLabel}>{t('Create post')}</span>
<Icon name="pencil-outline" class={styles.icon} /> <Icon name="pencil-outline" class={styles.icon} />
@ -223,18 +214,12 @@ export const HeaderAuth = (props: Props) => {
</div> </div>
</Show> </Show>
<Show when={isCreatePostButtonVisible() && !author()?.id}> <Show when={isCreatePostButtonVisible() && !session()?.access_token}>
<div <div class={clsx(styles.userControlItem, styles.userControlItemVerbose)}>
class={clsx(
styles.userControlItem,
styles.userControlItemVerbose,
// styles.userControlItemCreate,
)}
>
<a href={getPagePath(router, 'create')}> <a href={getPagePath(router, 'create')}>
<span class={styles.textLabel}>{t('Create post')}</span> <span class={styles.textLabel}>{t('Create post')}</span>
<Icon name="pencil-outline" class={styles.icon} /> <Icon name="pencil" class={styles.icon} />
<Icon name="pencil-outline-hover" class={clsx(styles.icon, styles.iconHover)} /> <Icon name="pencil" class={clsx(styles.icon, styles.iconHover)} />
</a> </a>
</div> </div>
</Show> </Show>
@ -242,24 +227,19 @@ export const HeaderAuth = (props: Props) => {
<Show <Show
when={isAuthenticatedControlsVisible()} when={isAuthenticatedControlsVisible()}
fallback={ fallback={
<Show when={!author()?.id}> <Show when={!session()?.access_token}>
<div class={clsx(styles.userControlItem, styles.userControlItemVerbose, 'loginbtn')}> <div class={clsx(styles.userControlItem, styles.userControlItemVerbose, 'loginbtn')}>
<a href="?m=auth&mode=login"> <a href="?m=auth&mode=login">
<span class={styles.textLabel}>{t('Enter')}</span> <span class={styles.textLabel}>{t('Enter')}</span>
<Icon name="key" class={styles.icon} /> <Icon name="key" class={styles.icon} />
<Icon name="key" class={clsx(styles.icon, styles.iconHover)} /> {/*<Icon name="user-default" class={clsx(styles.icon, styles.iconHover)} />*/}
</a> </a>
</div> </div>
</Show> </Show>
} }
> >
<Show when={!isSaveButtonVisible()}> <Show when={!isSaveButtonVisible()}>
<div <div class={clsx(styles.userControlItem, styles.userControlItemInbox)}>
class={clsx(
styles.userControlItem,
// styles.userControlItemInbox
)}
>
<a href={getPagePath(router, 'inbox')}> <a href={getPagePath(router, 'inbox')}>
<div classList={{ entered: page().path === '/inbox' }}> <div classList={{ entered: page().path === '/inbox' }}>
<Icon name="inbox-white" class={styles.icon} /> <Icon name="inbox-white" class={styles.icon} />
@ -271,7 +251,7 @@ export const HeaderAuth = (props: Props) => {
</Show> </Show>
</div> </div>
<Show when={author()?.id}> <Show when={session()?.access_token}>
<ProfilePopup <ProfilePopup
onVisibilityChange={(isVisible) => { onVisibilityChange={(isVisible) => {
props.setIsProfilePopupVisible(isVisible) props.setIsProfilePopupVisible(isVisible)

View File

@ -57,7 +57,7 @@ export const ProfileSettings = () => {
const [nameError, setNameError] = createSignal<string>() const [nameError, setNameError] = createSignal<string>()
const { form, submit, updateFormField, setForm } = useProfileForm() const { form, submit, updateFormField, setForm } = useProfileForm()
const { showSnackbar } = useSnackbar() const { showSnackbar } = useSnackbar()
const { loadAuthor } = useSession() const { loadAuthor, session } = useSession()
const { showConfirm } = useConfirm() const { showConfirm } = useConfirm()
createEffect(() => { createEffect(() => {
@ -140,7 +140,7 @@ export const ProfileSettings = () => {
setUploadError(false) setUploadError(false)
setIsUserpicUpdating(true) setIsUserpicUpdating(true)
const result = await handleImageUpload(uploadFile) const result = await handleImageUpload(uploadFile, session()?.access_token)
updateFormField('pic', result.url) updateFormField('pic', result.url)
setUserpicFile(null) setUserpicFile(null)

View File

@ -13,16 +13,20 @@ import { Loading } from '../../_shared/Loading'
import styles from './DraftsView.module.scss' import styles from './DraftsView.module.scss'
export const DraftsView = () => { export const DraftsView = () => {
const { session } = useSession() const { author, loadSession } = useSession()
const [drafts, setDrafts] = createSignal<Shout[]>([]) const [drafts, setDrafts] = createSignal<Shout[]>([])
createEffect( createEffect(
on( on(
() => session(), () => author(),
async (s) => { async (a) => {
if (s) { if (a) {
const loadedDrafts = await apiClient.getDrafts() const { shouts: loadedDrafts, error } = await apiClient.getDrafts()
setDrafts(loadedDrafts.reverse() || []) if (error) {
console.warn(error)
await loadSession()
}
setDrafts(loadedDrafts || [])
} }
}, },
), ),
@ -46,7 +50,7 @@ export const DraftsView = () => {
return ( return (
<div class={clsx(styles.DraftsView)}> <div class={clsx(styles.DraftsView)}>
<Show when={session()?.user?.id} fallback={<Loading />}> <Show when={author()?.id} fallback={<Loading />}>
<div class="wide-container"> <div class="wide-container">
<div class="row"> <div class="row">
<div class="col-md-19 col-lg-18 col-xl-16 offset-md-5"> <div class="col-md-19 col-lg-18 col-xl-16 offset-md-5">

View File

@ -5,6 +5,7 @@ import { clsx } from 'clsx'
import { JSX, Show, createSignal } from 'solid-js' import { JSX, Show, createSignal } from 'solid-js'
import { useLocalize } from '../../../context/localize' import { useLocalize } from '../../../context/localize'
import { useSession } from '../../../context/session'
import { UploadedFile } from '../../../pages/types' import { UploadedFile } from '../../../pages/types'
import { handleFileUpload } from '../../../utils/handleFileUpload' import { handleFileUpload } from '../../../utils/handleFileUpload'
import { handleImageUpload } from '../../../utils/handleImageUpload' import { handleImageUpload } from '../../../utils/handleImageUpload'
@ -27,6 +28,7 @@ export const DropArea = (props: Props) => {
const [dragActive, setDragActive] = createSignal(false) const [dragActive, setDragActive] = createSignal(false)
const [dropAreaError, setDropAreaError] = createSignal<string>() const [dropAreaError, setDropAreaError] = createSignal<string>()
const [loading, setLoading] = createSignal(false) const [loading, setLoading] = createSignal(false)
const { session } = useSession()
const runUpload = async (files) => { const runUpload = async (files) => {
try { try {
@ -35,7 +37,7 @@ export const DropArea = (props: Props) => {
const results: UploadedFile[] = [] const results: UploadedFile[] = []
for (const file of files) { for (const file of files) {
const handler = props.fileType === 'image' ? handleImageUpload : handleFileUpload const handler = props.fileType === 'image' ? handleImageUpload : handleFileUpload
const result = await handler(file) const result = await handler(file, session()?.access_token)
results.push(result) results.push(result)
} }
props.onUpload(results) props.onUpload(results)

View File

@ -19,6 +19,7 @@ import { Popover } from '../Popover'
import { SwiperRef } from './swiper' import { SwiperRef } from './swiper'
import { useSession } from '../../../context/session'
import styles from './Swiper.module.scss' import styles from './Swiper.module.scss'
const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor')) const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor'))
@ -36,7 +37,7 @@ export const EditorSwiper = (props: Props) => {
const [loading, setLoading] = createSignal(false) const [loading, setLoading] = createSignal(false)
const [slideIndex, setSlideIndex] = createSignal(0) const [slideIndex, setSlideIndex] = createSignal(0)
const [slideBody, setSlideBody] = createSignal<string>() const [slideBody, setSlideBody] = createSignal<string>()
const { session } = useSession()
const mainSwipeRef: { current: SwiperRef } = { current: null } const mainSwipeRef: { current: SwiperRef } = { current: null }
const thumbSwipeRef: { current: SwiperRef } = { current: null } const thumbSwipeRef: { current: SwiperRef } = { current: null }
@ -100,7 +101,7 @@ export const EditorSwiper = (props: Props) => {
setLoading(true) setLoading(true)
const results: UploadedFile[] = [] const results: UploadedFile[] = []
for (const file of selectedFiles) { for (const file of selectedFiles) {
const result = await handleImageUpload(file) const result = await handleImageUpload(file, session()?.access_token)
results.push(result) results.push(result)
} }
props.onImagesAdd(composeMediaItems(results)) props.onImagesAdd(composeMediaItems(results))

View File

@ -175,7 +175,7 @@ export const apiClient = {
console.debug('[graphql.client.core] deleteShout:', response) console.debug('[graphql.client.core] deleteShout:', response)
}, },
getDrafts: async (): Promise<Shout[]> => { getDrafts: async (): Promise<CommonResult> => {
const response = await apiClient.private.query(draftsLoad, {}).toPromise() const response = await apiClient.private.query(draftsLoad, {}).toPromise()
console.debug('[graphql.client.core] getDrafts:', response) console.debug('[graphql.client.core] getDrafts:', response)
return response.data.get_shouts_drafts return response.data.get_shouts_drafts

View File

@ -3,6 +3,8 @@ import { gql } from '@urql/core'
export default gql` export default gql`
query LoadDraftsQuery { query LoadDraftsQuery {
get_shouts_drafts { get_shouts_drafts {
error
shouts {
id id
title title
subtitle subtitle
@ -40,4 +42,5 @@ export default gql`
} }
} }
} }
}
` `

View File

@ -1,28 +0,0 @@
import { gql } from '@urql/core'
export default gql`
query MySubscriptionsQuery {
get_my_followed {
topics {
id
title
body
slug
}
authors {
id
name
slug
pic
created_at
}
communities {
id
name
slug
pic
created_at
}
}
}
`

View File

@ -37,7 +37,8 @@ export const EditPage = () => {
const fail = async (error: string) => { const fail = async (error: string) => {
console.error(error) console.error(error)
await snackbar?.showSnackbar({ type: 'error', body: t(error) }) const errorMessage = error === 'forbidden' ? "You can't edit this post" : error
await snackbar?.showSnackbar({ type: 'error', body: t(errorMessage) })
redirectPage(router, 'drafts') redirectPage(router, 'drafts')
} }

View File

@ -1,7 +1,7 @@
import type { PageContextBuiltInClientWithClientRouting } from 'vike/types' import type { PageContextBuiltInClientWithClientRouting } from 'vike/types'
import type { PageContext } from './types' import type { PageContext } from './types'
// import * as Sentry from '@sentry/browser' import { init as SentryInit, replayIntegration } from '@sentry/browser'
import i18next from 'i18next' import i18next from 'i18next'
import HttpApi from 'i18next-http-backend' import HttpApi from 'i18next-http-backend'
import ICU from 'i18next-icu' import ICU from 'i18next-icu'
@ -9,7 +9,7 @@ import { hydrate } from 'solid-js/web'
import { App } from '../components/App' import { App } from '../components/App'
import { initRouter } from '../stores/router' import { initRouter } from '../stores/router'
// import { SENTRY_DSN } from '../utils/config' import { GLITCHTIP_DSN } from '../utils/config'
import { resolveHydrationPromise } from '../utils/hydrationPromise' import { resolveHydrationPromise } from '../utils/hydrationPromise'
let layoutReady = false let layoutReady = false
@ -20,13 +20,16 @@ export const render = async (pageContext: PageContextBuiltInClientWithClientRout
const { pathname, search } = window.location const { pathname, search } = window.location
const searchParams = Object.fromEntries(new URLSearchParams(search)) const searchParams = Object.fromEntries(new URLSearchParams(search))
initRouter(pathname, searchParams) initRouter(pathname, searchParams)
/*
if (SENTRY_DSN) { SentryInit({
Sentry.init({ dsn: GLITCHTIP_DSN,
dsn: SENTRY_DSN, tracesSampleRate: 0.01,
integrations: [replayIntegration()],
// Session Replay
replaysSessionSampleRate: 0.1, // This sets the sample rate at 10%. You may want to change it to 100% while in development and then sample at a lower rate in production.
replaysOnErrorSampleRate: 1.0, // If you're not already sampling the entire session, change the sample rate to 100% when sampling sessions where errors occur.
}) })
}
*/
// eslint-disable-next-line import/no-named-as-default-member // eslint-disable-next-line import/no-named-as-default-member
await i18next await i18next
.use(HttpApi) .use(HttpApi)

View File

@ -5,6 +5,7 @@ export const cdnUrl = 'https://cdn.discours.io'
export const thumborUrl = import.meta.env.PUBLIC_THUMBOR_URL || defaultThumborUrl export const thumborUrl = import.meta.env.PUBLIC_THUMBOR_URL || defaultThumborUrl
export const SENTRY_DSN = import.meta.env.PUBLIC_SENTRY_DSN || '' export const SENTRY_DSN = import.meta.env.PUBLIC_SENTRY_DSN || ''
export const GLITCHTIP_DSN = import.meta.env.PUBLIC_GLITCHTIP_DSN || ''
const defaultSearchUrl = 'https://search.discours.io' const defaultSearchUrl = 'https://search.discours.io'
export const searchUrl = import.meta.env.PUBLIC_SEARCH_URL || defaultSearchUrl export const searchUrl = import.meta.env.PUBLIC_SEARCH_URL || defaultSearchUrl

View File

@ -5,12 +5,15 @@ import { UploadedFile } from '../pages/types'
const apiBaseUrl = 'https://core.discours.io' const apiBaseUrl = 'https://core.discours.io'
const apiUrl = `${apiBaseUrl}/upload` const apiUrl = `${apiBaseUrl}/upload`
export const handleFileUpload = async (uploadFile: UploadFile): Promise<UploadedFile> => { export const handleFileUpload = async (uploadFile: UploadFile, token: string): Promise<UploadedFile> => {
const formData = new FormData() const formData = new FormData()
formData.append('file', uploadFile.file, uploadFile.name) formData.append('file', uploadFile.file, uploadFile.name)
const response = await fetch(apiUrl, { const response = await fetch(apiUrl, {
method: 'POST', method: 'POST',
body: formData, body: formData,
headers: {
Authorization: token,
},
}) })
return response.json() return response.json()
} }

View File

@ -4,12 +4,14 @@ import { UploadedFile } from '../pages/types'
import { thumborUrl } from './config' import { thumborUrl } from './config'
export const handleImageUpload = async (uploadFile: UploadFile): Promise<UploadedFile> => { export const handleImageUpload = async (uploadFile: UploadFile, token: string): Promise<UploadedFile> => {
const formData = new FormData() const formData = new FormData()
formData.append('media', uploadFile.file, uploadFile.name) formData.append('media', uploadFile.file, uploadFile.name)
const headers = token ? { Authorization: token } : {}
const response = await fetch(`${thumborUrl}/image`, { const response = await fetch(`${thumborUrl}/image`, {
method: 'POST', method: 'POST',
body: formData, body: formData,
headers,
}) })
const location = response.headers.get('Location') const location = response.headers.get('Location')

View File

@ -70,6 +70,7 @@ export default defineConfig(({ mode, command }) => {
https: {}, https: {},
port: 3000, port: 3000,
}, },
sourcemap: isDev,
css: { css: {
devSourcemap: isDev, devSourcemap: isDev,
preprocessorOptions: { preprocessorOptions: {