Merge remote-tracking branch 'hub/dev' into hotfix/posting
This commit is contained in:
commit
01d11aca5b
|
@ -29,6 +29,7 @@ import { VideoPlayer } from '../_shared/VideoPlayer'
|
||||||
import { AuthorBadge } from '../Author/AuthorBadge'
|
import { AuthorBadge } from '../Author/AuthorBadge'
|
||||||
import { CardTopic } from '../Feed/CardTopic'
|
import { CardTopic } from '../Feed/CardTopic'
|
||||||
import { FeedArticlePopup } from '../Feed/FeedArticlePopup'
|
import { FeedArticlePopup } from '../Feed/FeedArticlePopup'
|
||||||
|
import { Modal } from '../Nav/Modal'
|
||||||
import { TableOfContents } from '../TableOfContents'
|
import { TableOfContents } from '../TableOfContents'
|
||||||
|
|
||||||
import { AudioHeader } from './AudioHeader'
|
import { AudioHeader } from './AudioHeader'
|
||||||
|
@ -79,7 +80,7 @@ export const FullArticle = (props: Props) => {
|
||||||
actions: { requireAuthentication },
|
actions: { requireAuthentication },
|
||||||
} = useSession()
|
} = useSession()
|
||||||
|
|
||||||
const formattedDate = createMemo(() => formatDate(new Date(props.article.created_at * 1000)))
|
const formattedDate = createMemo(() => formatDate(new Date(props.article.published_at * 1000)))
|
||||||
|
|
||||||
const mainTopic = createMemo(() => {
|
const mainTopic = createMemo(() => {
|
||||||
const main_topic_slug = props.article.topics.length > 0 ? props.article.main_topic : null
|
const main_topic_slug = props.article.topics.length > 0 ? props.article.main_topic : null
|
||||||
|
@ -308,6 +309,8 @@ export const FullArticle = (props: Props) => {
|
||||||
const aspectRatio = width / height
|
const aspectRatio = width / height
|
||||||
iframe.style.width = `${containerWidth}px`
|
iframe.style.width = `${containerWidth}px`
|
||||||
iframe.style.height = `${Math.round(containerWidth / aspectRatio) + 40}px`
|
iframe.style.height = `${Math.round(containerWidth / aspectRatio) + 40}px`
|
||||||
|
} else {
|
||||||
|
iframe.style.height = `${containerWidth}px`
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -618,7 +621,9 @@ export const FullArticle = (props: Props) => {
|
||||||
<Show when={selectedImage()}>
|
<Show when={selectedImage()}>
|
||||||
<Lightbox image={selectedImage()} onClose={handleLightboxClose} />
|
<Lightbox image={selectedImage()} onClose={handleLightboxClose} />
|
||||||
</Show>
|
</Show>
|
||||||
<InviteMembers variant={'coauthors'} title={t('Invite experts')} />
|
<Modal variant="medium" name="inviteMembers">
|
||||||
|
<InviteMembers variant={'coauthors'} title={t('Invite experts')} />
|
||||||
|
</Modal>
|
||||||
<ShareModal
|
<ShareModal
|
||||||
title={props.article.title}
|
title={props.article.title}
|
||||||
description={description}
|
description={description}
|
||||||
|
|
|
@ -94,11 +94,9 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
const mainTopicTitle =
|
const mainTopicTitle =
|
||||||
mainTopicSlug && lang() === 'en' ? mainTopicSlug.replace(/-/, ' ') : mainTopic?.title || ''
|
mainTopicSlug && lang() === 'en' ? mainTopicSlug.replace(/-/, ' ') : mainTopic?.title || ''
|
||||||
|
|
||||||
const formattedDate = createMemo<string>(() => {
|
const formattedDate = createMemo<string>(() =>
|
||||||
let r = ''
|
props.article.published_at ? formatDate(new Date(props.article.published_at * 1000)) : '',
|
||||||
if (props.article.created_at) r = formatDate(new Date(props.article.created_at * 1000))
|
)
|
||||||
return r
|
|
||||||
})
|
|
||||||
|
|
||||||
const { title, subtitle } = getTitleAndSubtitle(props.article)
|
const { title, subtitle } = getTitleAndSubtitle(props.article)
|
||||||
|
|
||||||
|
|
|
@ -188,11 +188,10 @@
|
||||||
line-height: 16px;
|
line-height: 16px;
|
||||||
margin-top: 0.3em;
|
margin-top: 0.3em;
|
||||||
|
|
||||||
/* Red/500 */
|
color: var(--danger-color);
|
||||||
color: #d00820;
|
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: #d00820;
|
color: var(--danger-color);
|
||||||
border-color: #d00820;
|
border-color: #d00820;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import type { AuthModalSearchParams } from './types'
|
import type { AuthModalSearchParams } from './types'
|
||||||
|
|
||||||
|
import { ApiResponse, ForgotPasswordResponse } from '@authorizerdev/authorizer-js'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { createSignal, JSX, Show } from 'solid-js'
|
import { createSignal, JSX, Show } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { useSession } from '../../../context/session'
|
import { useSession } from '../../../context/session'
|
||||||
// import { ApiError } from '../../../graphql/error'
|
|
||||||
import { useRouter } from '../../../stores/router'
|
import { useRouter } from '../../../stores/router'
|
||||||
import { validateEmail } from '../../../utils/validateEmail'
|
import { validateEmail } from '../../../utils/validateEmail'
|
||||||
|
|
||||||
|
@ -29,16 +29,14 @@ export const ForgotPasswordForm = () => {
|
||||||
const {
|
const {
|
||||||
actions: { forgotPassword },
|
actions: { forgotPassword },
|
||||||
} = useSession()
|
} = useSession()
|
||||||
const [submitError, setSubmitError] = createSignal('')
|
|
||||||
const [isSubmitting, setIsSubmitting] = createSignal(false)
|
const [isSubmitting, setIsSubmitting] = createSignal(false)
|
||||||
const [validationErrors, setValidationErrors] = createSignal<ValidationErrors>({})
|
const [validationErrors, setValidationErrors] = createSignal<ValidationErrors>({})
|
||||||
const [isUserNotFount, setIsUserNotFound] = createSignal(false)
|
const [isUserNotFound, setIsUserNotFound] = createSignal(false)
|
||||||
const authFormRef: { current: HTMLFormElement } = { current: null }
|
const authFormRef: { current: HTMLFormElement } = { current: null }
|
||||||
const [message, setMessage] = createSignal<string>('')
|
const [message, setMessage] = createSignal<string>('')
|
||||||
|
|
||||||
const handleSubmit = async (event: Event) => {
|
const handleSubmit = async (event: Event) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
setSubmitError('')
|
|
||||||
setIsUserNotFound(false)
|
setIsUserNotFound(false)
|
||||||
const newValidationErrors: ValidationErrors = {}
|
const newValidationErrors: ValidationErrors = {}
|
||||||
|
|
||||||
|
@ -66,15 +64,8 @@ export const ForgotPasswordForm = () => {
|
||||||
redirect_uri: window.location.origin,
|
redirect_uri: window.location.origin,
|
||||||
})
|
})
|
||||||
console.debug('[ForgotPasswordForm] authorizer response:', data)
|
console.debug('[ForgotPasswordForm] authorizer response:', data)
|
||||||
setMessage(data.message)
|
if (errors && errors.some((error) => error.message.includes('bad user credentials'))) {
|
||||||
|
|
||||||
console.warn(errors)
|
|
||||||
if (errors.some((e) => e.cause === 'user_not_found')) {
|
|
||||||
setIsUserNotFound(true)
|
setIsUserNotFound(true)
|
||||||
return
|
|
||||||
} else {
|
|
||||||
const errorText = errors.map((e) => e.message).join(' ') // FIXME
|
|
||||||
setSubmitError(errorText)
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
|
@ -111,37 +102,27 @@ export const ForgotPasswordForm = () => {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<label for="email">{t('Email')}</label>
|
<label for="email">{t('Email')}</label>
|
||||||
|
<Show when={isUserNotFound()}>
|
||||||
|
<div class={styles.validationError}>
|
||||||
|
{t("We can't find you, check email or")}{' '}
|
||||||
|
<span
|
||||||
|
class={'link'}
|
||||||
|
onClick={() =>
|
||||||
|
changeSearchParams({
|
||||||
|
mode: 'login',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t('register')}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
<Show when={validationErrors().email}>
|
||||||
|
<div class={styles.validationError}>{validationErrors().email}</div>
|
||||||
|
</Show>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Show when={submitError()}>
|
<div style={{ 'margin-top': '5rem' }}>
|
||||||
<div class={styles.authInfo}>
|
|
||||||
<ul>
|
|
||||||
<li class={styles.warn}>{submitError()}</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
|
|
||||||
<Show when={isUserNotFount()}>
|
|
||||||
<div class={styles.authSubtitle}>
|
|
||||||
{t("We can't find you, check email or")}{' '}
|
|
||||||
<a
|
|
||||||
href="#"
|
|
||||||
onClick={(event) => {
|
|
||||||
event.preventDefault()
|
|
||||||
changeSearchParams({
|
|
||||||
mode: 'register',
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t('register')}
|
|
||||||
</a>
|
|
||||||
<Show when={validationErrors().email}>
|
|
||||||
<div class={styles.validationError}>{validationErrors().email}</div>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<button
|
<button
|
||||||
class={clsx('button', styles.submitButton)}
|
class={clsx('button', styles.submitButton)}
|
||||||
disabled={isSubmitting() || Boolean(message())}
|
disabled={isSubmitting() || Boolean(message())}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type { AuthModalSearchParams } from './types'
|
import type { AuthModalSearchParams } from './types'
|
||||||
|
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { createSignal, Show } from 'solid-js'
|
import { createEffect, createSignal, Show } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { useSession } from '../../../context/session'
|
import { useSession } from '../../../context/session'
|
||||||
|
@ -106,26 +106,22 @@ export const LoginForm = () => {
|
||||||
setIsSubmitting(true)
|
setIsSubmitting(true)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await signIn({ email: email(), password: password() })
|
const { errors } = await signIn({ email: email(), password: password() })
|
||||||
|
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 {
|
||||||
|
setSubmitError(t('Error'))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
hideModal()
|
hideModal()
|
||||||
|
|
||||||
showSnackbar({ body: t('Welcome!') })
|
showSnackbar({ body: t('Welcome!') })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
if (error instanceof ApiError) {
|
|
||||||
if (error.code === 'email_not_confirmed') {
|
|
||||||
setSubmitError(t('Please, confirm email'))
|
|
||||||
setIsEmailNotConfirmed(true)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (error.code === 'user_not_found') {
|
|
||||||
setSubmitError(t('Something went wrong, check email and password'))
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setSubmitError(error.message)
|
setSubmitError(error.message)
|
||||||
} finally {
|
} finally {
|
||||||
setIsSubmitting(false)
|
setIsSubmitting(false)
|
||||||
|
@ -170,6 +166,11 @@ export const LoginForm = () => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PasswordField variant={'login'} onInput={(value) => handlePasswordInput(value)} />
|
<PasswordField variant={'login'} onInput={(value) => handlePasswordInput(value)} />
|
||||||
|
<Show when={validationErrors().password}>
|
||||||
|
<div class={styles.validationError} style={{ position: 'static', 'font-size': '1.4rem' }}>
|
||||||
|
{validationErrors().password}
|
||||||
|
</div>
|
||||||
|
</Show>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<button class={clsx('button', styles.submitButton)} disabled={isSubmitting()} type="submit">
|
<button class={clsx('button', styles.submitButton)} disabled={isSubmitting()} type="submit">
|
||||||
|
|
|
@ -6,7 +6,6 @@ import { Show, createSignal } from 'solid-js'
|
||||||
|
|
||||||
import { useLocalize } from '../../../context/localize'
|
import { useLocalize } from '../../../context/localize'
|
||||||
import { useSession } from '../../../context/session'
|
import { useSession } from '../../../context/session'
|
||||||
// import { ApiError } from '../../../graphql/error'
|
|
||||||
import { checkEmail, useEmailChecks } from '../../../stores/emailChecks'
|
import { checkEmail, useEmailChecks } from '../../../stores/emailChecks'
|
||||||
import { useRouter } from '../../../stores/router'
|
import { useRouter } from '../../../stores/router'
|
||||||
import { hideModal } from '../../../stores/ui'
|
import { hideModal } from '../../../stores/ui'
|
||||||
|
@ -113,34 +112,30 @@ export const RegisterForm = () => {
|
||||||
confirm_password: password(),
|
confirm_password: password(),
|
||||||
redirect_uri: window.location.origin,
|
redirect_uri: window.location.origin,
|
||||||
}
|
}
|
||||||
await signUp(opts)
|
const { errors } = await signUp(opts)
|
||||||
|
if (errors && errors.some((error) => error.message.includes('has already signed up'))) {
|
||||||
|
setValidationErrors((prev) => ({
|
||||||
|
...prev,
|
||||||
|
email: (
|
||||||
|
<>
|
||||||
|
{t('User with this email already exists')},{' '}
|
||||||
|
<span
|
||||||
|
class={'link'}
|
||||||
|
onClick={() =>
|
||||||
|
changeSearchParams({
|
||||||
|
mode: 'login',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t('sign in')}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
}))
|
||||||
|
}
|
||||||
setIsSuccess(true)
|
setIsSuccess(true)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
if (error) {
|
|
||||||
if (error.message.includes('has already signed up')) {
|
|
||||||
setValidationErrors((errors) => ({
|
|
||||||
...errors,
|
|
||||||
email: (
|
|
||||||
<>
|
|
||||||
{t('User with this email already exists')},{' '}
|
|
||||||
<span
|
|
||||||
class={'link'}
|
|
||||||
onClick={() =>
|
|
||||||
changeSearchParams({
|
|
||||||
mode: 'login',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{t('sign in')}
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
console.error(error)
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
setIsSubmitting(false)
|
setIsSubmitting(false)
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,13 +29,7 @@ const getSearchCoincidences = ({ str, intersection }: { str: string; intersectio
|
||||||
const prepareSearchResults = (list: Shout[], searchValue: string) =>
|
const prepareSearchResults = (list: Shout[], searchValue: string) =>
|
||||||
list.sort(byScore()).map((article, index) => ({
|
list.sort(byScore()).map((article, index) => ({
|
||||||
...article,
|
...article,
|
||||||
body: article.body,
|
|
||||||
cover: article.cover,
|
|
||||||
created_at: article.created_at,
|
|
||||||
id: index,
|
id: index,
|
||||||
slug: article.slug,
|
|
||||||
authors: article.authors,
|
|
||||||
topics: article.topics,
|
|
||||||
title: article.title
|
title: article.title
|
||||||
? getSearchCoincidences({
|
? getSearchCoincidences({
|
||||||
str: article.title,
|
str: article.title,
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { createStore } from 'solid-js/store'
|
||||||
|
|
||||||
import { ShoutForm, useEditorContext } from '../../context/editor'
|
import { ShoutForm, useEditorContext } from '../../context/editor'
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
import { ShoutVisibility, type Shout, type Topic } from '../../graphql/schema/core.gen'
|
import { type Shout, type Topic } from '../../graphql/schema/core.gen'
|
||||||
import { LayoutType, MediaItem } from '../../pages/types'
|
import { LayoutType, MediaItem } from '../../pages/types'
|
||||||
import { useRouter } from '../../stores/router'
|
import { useRouter } from '../../stores/router'
|
||||||
import { clone } from '../../utils/clone'
|
import { clone } from '../../utils/clone'
|
||||||
|
@ -182,10 +182,10 @@ export const EditView = (props: Props) => {
|
||||||
const hasChanges = !deepEqual(form, prevForm)
|
const hasChanges = !deepEqual(form, prevForm)
|
||||||
if (hasChanges) {
|
if (hasChanges) {
|
||||||
setSaving(true)
|
setSaving(true)
|
||||||
if (props.shout?.visibility === ShoutVisibility.Authors) {
|
if (props.shout?.published_at) {
|
||||||
await saveDraft(form)
|
|
||||||
} else {
|
|
||||||
saveDraftToLocalStorage(form)
|
saveDraftToLocalStorage(form)
|
||||||
|
} else {
|
||||||
|
await saveDraft(form)
|
||||||
}
|
}
|
||||||
setPrevForm(clone(form))
|
setPrevForm(clone(form))
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|
|
@ -45,7 +45,7 @@ export const Expo = (props: Props) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
const getLoadShoutsFilters = (additionalFilters: LoadShoutsFilters = {}): LoadShoutsFilters => {
|
const getLoadShoutsFilters = (additionalFilters: LoadShoutsFilters = {}): LoadShoutsFilters => {
|
||||||
const filters = { published: true, ...additionalFilters }
|
const filters = { featured: true, ...additionalFilters }
|
||||||
|
|
||||||
if (!filters.layouts) filters.layouts = []
|
if (!filters.layouts) filters.layouts = []
|
||||||
if (props.layout) {
|
if (props.layout) {
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import type { Author, LoadShoutsOptions, Reaction, Shout } from '../../../graphql/schema/core.gen'
|
|
||||||
|
|
||||||
import { getPagePath } from '@nanostores/router'
|
import { getPagePath } from '@nanostores/router'
|
||||||
import { Meta } from '@solidjs/meta'
|
import { Meta } from '@solidjs/meta'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
|
@ -9,6 +7,12 @@ import { useLocalize } from '../../../context/localize'
|
||||||
import { useReactions } from '../../../context/reactions'
|
import { useReactions } from '../../../context/reactions'
|
||||||
import { useSession } from '../../../context/session'
|
import { useSession } from '../../../context/session'
|
||||||
import { apiClient } from '../../../graphql/client/core'
|
import { apiClient } from '../../../graphql/client/core'
|
||||||
|
import {
|
||||||
|
type Author,
|
||||||
|
type LoadShoutsOptions,
|
||||||
|
type Reaction,
|
||||||
|
type Shout,
|
||||||
|
} from '../../../graphql/schema/core.gen'
|
||||||
import { router, useRouter } from '../../../stores/router'
|
import { router, useRouter } from '../../../stores/router'
|
||||||
import { showModal } from '../../../stores/ui'
|
import { showModal } from '../../../stores/ui'
|
||||||
import { useArticlesStore, resetSortedArticles } from '../../../stores/zine/articles'
|
import { useArticlesStore, resetSortedArticles } from '../../../stores/zine/articles'
|
||||||
|
@ -35,7 +39,7 @@ export const FEED_PAGE_SIZE = 20
|
||||||
const UNRATED_ARTICLES_COUNT = 5
|
const UNRATED_ARTICLES_COUNT = 5
|
||||||
|
|
||||||
type FeedPeriod = 'week' | 'month' | 'year'
|
type FeedPeriod = 'week' | 'month' | 'year'
|
||||||
type VisibilityMode = 'all' | 'community' | 'public'
|
type VisibilityMode = 'all' | 'community' | 'featured'
|
||||||
|
|
||||||
type PeriodItem = {
|
type PeriodItem = {
|
||||||
value: FeedPeriod
|
value: FeedPeriod
|
||||||
|
@ -96,7 +100,7 @@ export const FeedView = (props: Props) => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
|
|
||||||
const monthPeriod: PeriodItem = { value: 'month', title: t('This month') }
|
const monthPeriod: PeriodItem = { value: 'month', title: t('This month') }
|
||||||
const visibilityAll = { value: 'public', title: t('All') }
|
const visibilityAll = { value: 'featured', title: t('All') }
|
||||||
|
|
||||||
const periods: PeriodItem[] = [
|
const periods: PeriodItem[] = [
|
||||||
{ value: 'week', title: t('This week') },
|
{ value: 'week', title: t('This week') },
|
||||||
|
@ -106,7 +110,7 @@ export const FeedView = (props: Props) => {
|
||||||
|
|
||||||
const visibilities: VisibilityItem[] = [
|
const visibilities: VisibilityItem[] = [
|
||||||
{ value: 'community', title: t('All') },
|
{ value: 'community', title: t('All') },
|
||||||
{ value: 'public', title: t('Published') },
|
{ value: 'featured', title: t('Published') },
|
||||||
]
|
]
|
||||||
|
|
||||||
const { page, searchParams, changeSearchParams } = useRouter<FeedSearchParams>()
|
const { page, searchParams, changeSearchParams } = useRouter<FeedSearchParams>()
|
||||||
|
@ -190,7 +194,10 @@ export const FeedView = (props: Props) => {
|
||||||
if (visibilityMode === 'all') {
|
if (visibilityMode === 'all') {
|
||||||
options.filters = { ...options.filters }
|
options.filters = { ...options.filters }
|
||||||
} else if (visibilityMode) {
|
} else if (visibilityMode) {
|
||||||
options.filters = { ...options.filters, published: visibilityMode === 'public' }
|
options.filters = {
|
||||||
|
...options.filters,
|
||||||
|
featured: visibilityMode === 'featured',
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (searchParams().by && searchParams().by !== 'publish_date') {
|
if (searchParams().by && searchParams().by !== 'publish_date') {
|
||||||
|
|
|
@ -60,7 +60,7 @@ export const HomeView = (props: Props) => {
|
||||||
loadTopMonthArticles()
|
loadTopMonthArticles()
|
||||||
if (sortedArticles().length < PRERENDERED_ARTICLES_COUNT + CLIENT_LOAD_ARTICLES_COUNT) {
|
if (sortedArticles().length < PRERENDERED_ARTICLES_COUNT + CLIENT_LOAD_ARTICLES_COUNT) {
|
||||||
const { hasMore } = await loadShouts({
|
const { hasMore } = await loadShouts({
|
||||||
filters: { published: true },
|
filters: { featured: true },
|
||||||
limit: CLIENT_LOAD_ARTICLES_COUNT,
|
limit: CLIENT_LOAD_ARTICLES_COUNT,
|
||||||
offset: sortedArticles().length,
|
offset: sortedArticles().length,
|
||||||
})
|
})
|
||||||
|
@ -80,7 +80,7 @@ export const HomeView = (props: Props) => {
|
||||||
saveScrollPosition()
|
saveScrollPosition()
|
||||||
|
|
||||||
const { hasMore } = await loadShouts({
|
const { hasMore } = await loadShouts({
|
||||||
filters: { published: true },
|
filters: { featured: true },
|
||||||
limit: LOAD_MORE_PAGE_SIZE,
|
limit: LOAD_MORE_PAGE_SIZE,
|
||||||
offset: sortedArticles().length,
|
offset: sortedArticles().length,
|
||||||
})
|
})
|
||||||
|
|
|
@ -111,7 +111,7 @@ export const InviteMembers = (props: Props) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal variant="medium" name="inviteMembers">
|
<>
|
||||||
<h2>{props.title || t('Invite collaborators')}</h2>
|
<h2>{props.title || t('Invite collaborators')}</h2>
|
||||||
<div class={clsx(styles.InviteMembers)}>
|
<div class={clsx(styles.InviteMembers)}>
|
||||||
<div class={styles.searchHeader}>
|
<div class={styles.searchHeader}>
|
||||||
|
@ -182,6 +182,6 @@ export const InviteMembers = (props: Props) => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { Accessor, createContext, createSignal, useContext } from 'solid-js'
|
||||||
import { createStore, SetStoreFunction } from 'solid-js/store'
|
import { createStore, SetStoreFunction } from 'solid-js/store'
|
||||||
|
|
||||||
import { apiClient } from '../graphql/client/core'
|
import { apiClient } from '../graphql/client/core'
|
||||||
import { ShoutVisibility, Topic, TopicInput } from '../graphql/schema/core.gen'
|
import { Topic, TopicInput } from '../graphql/schema/core.gen'
|
||||||
import { router, useRouter } from '../stores/router'
|
import { router, useRouter } from '../stores/router'
|
||||||
import { slugify } from '../utils/slugify'
|
import { slugify } from '../utils/slugify'
|
||||||
|
|
||||||
|
@ -158,10 +158,10 @@ export const EditorProvider = (props: { children: JSX.Element }) => {
|
||||||
const shout = await updateShout(formToSave, { publish: false })
|
const shout = await updateShout(formToSave, { publish: false })
|
||||||
removeDraftFromLocalStorage(formToSave.shoutId)
|
removeDraftFromLocalStorage(formToSave.shoutId)
|
||||||
|
|
||||||
if (shout.visibility === ShoutVisibility.Authors) {
|
if (shout.published_at) {
|
||||||
openPage(router, 'drafts')
|
|
||||||
} else {
|
|
||||||
openPage(router, 'article', { slug: shout.slug })
|
openPage(router, 'article', { slug: shout.slug })
|
||||||
|
} else {
|
||||||
|
openPage(router, 'drafts')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[saveShout]', error)
|
console.error('[saveShout]', error)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { createEffect, createSignal, createContext, Accessor, useContext, JSX, onMount } from 'solid-js'
|
import { createEffect, createSignal, createContext, Accessor, useContext, JSX } from 'solid-js'
|
||||||
import { createStore } from 'solid-js/store'
|
import { createStore } from 'solid-js/store'
|
||||||
|
|
||||||
import { apiClient } from '../graphql/client/core'
|
import { apiClient } from '../graphql/client/core'
|
||||||
|
|
|
@ -59,8 +59,8 @@ export type SessionContextType = {
|
||||||
callback: (() => Promise<void>) | (() => void),
|
callback: (() => Promise<void>) | (() => void),
|
||||||
modalSource: AuthModalSource,
|
modalSource: AuthModalSource,
|
||||||
) => void
|
) => void
|
||||||
signUp: (params: SignupInput) => Promise<AuthToken | void>
|
signUp: (params: SignupInput) => Promise<{ data: AuthToken; errors: Error[] }>
|
||||||
signIn: (params: LoginInput) => Promise<void>
|
signIn: (params: LoginInput) => Promise<{ data: AuthToken; errors: Error[] }>
|
||||||
signOut: () => Promise<void>
|
signOut: () => Promise<void>
|
||||||
oauth: (provider: string) => Promise<void>
|
oauth: (provider: string) => Promise<void>
|
||||||
forgotPassword: (
|
forgotPassword: (
|
||||||
|
@ -273,16 +273,20 @@ export const SessionProvider = (props: {
|
||||||
})
|
})
|
||||||
|
|
||||||
// authorizer api proxy methods
|
// authorizer api proxy methods
|
||||||
|
const authenticate = async (authFunction, params) => {
|
||||||
|
const resp = await authFunction(params)
|
||||||
|
console.debug('[context.session] authenticate:', resp)
|
||||||
|
if (resp?.data && !resp.errors) {
|
||||||
|
setSession(resp.data)
|
||||||
|
}
|
||||||
|
return { data: resp?.data, errors: resp?.errors }
|
||||||
|
}
|
||||||
const signUp = async (params: SignupInput) => {
|
const signUp = async (params: SignupInput) => {
|
||||||
const authResult: ApiResponse<AuthToken> = await authorizer().signup(params)
|
return authenticate(authorizer().signup, params)
|
||||||
if (authResult?.data) setSession(authResult.data)
|
|
||||||
if (authResult?.errors) console.error(authResult.errors)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const signIn = async (params: LoginInput) => {
|
const signIn = async (params: LoginInput) => {
|
||||||
const authResult: ApiResponse<AuthToken> = await authorizer().login(params)
|
return authenticate(authorizer().login, params)
|
||||||
if (authResult?.data) setSession(authResult.data)
|
|
||||||
if (authResult?.errors) console.error(authResult.errors)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const signOut = async () => {
|
const signOut = async () => {
|
||||||
|
|
|
@ -28,7 +28,6 @@ export const inboxClient = {
|
||||||
|
|
||||||
loadChats: async (options: QueryLoad_ChatsArgs): Promise<Chat[]> => {
|
loadChats: async (options: QueryLoad_ChatsArgs): Promise<Chat[]> => {
|
||||||
const resp = await inboxClient.private.query(myChats, options).toPromise()
|
const resp = await inboxClient.private.query(myChats, options).toPromise()
|
||||||
console.log('!!! resp:', resp)
|
|
||||||
return resp.data.load_chats.chats
|
return resp.data.load_chats.chats
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,6 @@ export default gql`
|
||||||
title
|
title
|
||||||
lead
|
lead
|
||||||
description
|
description
|
||||||
visibility
|
|
||||||
subtitle
|
subtitle
|
||||||
slug
|
slug
|
||||||
layout
|
layout
|
||||||
|
@ -45,6 +44,7 @@ export default gql`
|
||||||
created_at
|
created_at
|
||||||
updated_at
|
updated_at
|
||||||
published_at
|
published_at
|
||||||
|
featured_at
|
||||||
stat {
|
stat {
|
||||||
viewed
|
viewed
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ export default gql`
|
||||||
}
|
}
|
||||||
created_at
|
created_at
|
||||||
published_at
|
published_at
|
||||||
|
featured_at
|
||||||
stat {
|
stat {
|
||||||
viewed
|
viewed
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@ export default gql`
|
||||||
}
|
}
|
||||||
created_at
|
created_at
|
||||||
published_at
|
published_at
|
||||||
|
featured_at
|
||||||
stat {
|
stat {
|
||||||
viewed
|
viewed
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ export default gql`
|
||||||
}
|
}
|
||||||
created_at
|
created_at
|
||||||
published_at
|
published_at
|
||||||
|
featured_at
|
||||||
stat {
|
stat {
|
||||||
viewed
|
viewed
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ export default gql`
|
||||||
}
|
}
|
||||||
created_at
|
created_at
|
||||||
published_at
|
published_at
|
||||||
|
featured_at
|
||||||
stat {
|
stat {
|
||||||
viewed
|
viewed
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,7 @@ export default gql`
|
||||||
}
|
}
|
||||||
created_at
|
created_at
|
||||||
published_at
|
published_at
|
||||||
|
featured_at
|
||||||
stat {
|
stat {
|
||||||
viewed
|
viewed
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,7 @@ export default gql`
|
||||||
}
|
}
|
||||||
created_at
|
created_at
|
||||||
published_at
|
published_at
|
||||||
|
featured_at
|
||||||
stat {
|
stat {
|
||||||
viewed
|
viewed
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,7 @@ export default gql`
|
||||||
}
|
}
|
||||||
created_at
|
created_at
|
||||||
published_at
|
published_at
|
||||||
|
featured_at
|
||||||
stat {
|
stat {
|
||||||
viewed
|
viewed
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ export const onBeforeRender = async (pageContext: PageContext) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const authorShouts = await apiClient.getShouts({
|
const authorShouts = await apiClient.getShouts({
|
||||||
filters: { author: slug, published: false },
|
filters: { author: slug, featured: false },
|
||||||
limit: PRERENDERED_ARTICLES_COUNT,
|
limit: PRERENDERED_ARTICLES_COUNT,
|
||||||
})
|
})
|
||||||
const pageProps: PageProps = { author, authorShouts, seo: { title: author.name } }
|
const pageProps: PageProps = { author, authorShouts, seo: { title: author.name } }
|
||||||
|
|
|
@ -21,7 +21,7 @@ export const AuthorPage = (props: PageProps) => {
|
||||||
const preload = () => {
|
const preload = () => {
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
loadShouts({
|
loadShouts({
|
||||||
filters: { author: slug(), published: false },
|
filters: { author: slug(), featured: false },
|
||||||
limit: PRERENDERED_ARTICLES_COUNT,
|
limit: PRERENDERED_ARTICLES_COUNT,
|
||||||
}),
|
}),
|
||||||
loadAuthor({ slug: slug() }),
|
loadAuthor({ slug: slug() }),
|
||||||
|
|
|
@ -13,7 +13,7 @@ const handleFeedLoadShouts = (options: LoadShoutsOptions) => {
|
||||||
return loadShouts({
|
return loadShouts({
|
||||||
...options,
|
...options,
|
||||||
filters: {
|
filters: {
|
||||||
published: false,
|
featured: false,
|
||||||
...options.filters,
|
...options.filters,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { apiClient } from '../graphql/client/core'
|
||||||
|
|
||||||
export const onBeforeRender = async (_pageContext: PageContext) => {
|
export const onBeforeRender = async (_pageContext: PageContext) => {
|
||||||
const homeShouts = await apiClient.getShouts({
|
const homeShouts = await apiClient.getShouts({
|
||||||
filters: { published: true },
|
filters: { featured: true },
|
||||||
limit: PRERENDERED_ARTICLES_COUNT,
|
limit: PRERENDERED_ARTICLES_COUNT,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ export const HomePage = (props: PageProps) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
loadShouts({ filters: { published: true }, limit: PRERENDERED_ARTICLES_COUNT }),
|
loadShouts({ filters: { featured: true }, limit: PRERENDERED_ARTICLES_COUNT }),
|
||||||
loadRandomTopics({ amount: RANDOM_TOPICS_COUNT }),
|
loadRandomTopics({ amount: RANDOM_TOPICS_COUNT }),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@ export const onBeforeRender = async (pageContext: PageContext) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const topicShouts = await apiClient.getShouts({
|
const topicShouts = await apiClient.getShouts({
|
||||||
filters: { topic: topic.slug, published: true },
|
filters: { topic: topic.slug, featured: true },
|
||||||
limit: PRERENDERED_ARTICLES_COUNT,
|
limit: PRERENDERED_ARTICLES_COUNT,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ export const TopicPage = (props: PageProps) => {
|
||||||
const preload = () =>
|
const preload = () =>
|
||||||
Promise.all([
|
Promise.all([
|
||||||
loadShouts({
|
loadShouts({
|
||||||
filters: { topic: slug(), published: true },
|
filters: { topic: slug(), featured: true },
|
||||||
limit: PRERENDERED_ARTICLES_COUNT,
|
limit: PRERENDERED_ARTICLES_COUNT,
|
||||||
offset: 0,
|
offset: 0,
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -131,7 +131,7 @@ export const loadShouts = async (
|
||||||
): Promise<{ hasMore: boolean; newShouts: Shout[] }> => {
|
): Promise<{ hasMore: boolean; newShouts: Shout[] }> => {
|
||||||
options.limit += 1
|
options.limit += 1
|
||||||
const newShouts = await apiClient.getShouts(options)
|
const newShouts = await apiClient.getShouts(options)
|
||||||
const hasMore = newShouts ?? newShouts.length === options.limit + 1
|
const hasMore = newShouts?.length === options.limit + 1
|
||||||
|
|
||||||
if (hasMore) {
|
if (hasMore) {
|
||||||
newShouts.splice(-1)
|
newShouts.splice(-1)
|
||||||
|
@ -148,7 +148,7 @@ export const loadMyFeed = async (
|
||||||
): Promise<{ hasMore: boolean; newShouts: Shout[] }> => {
|
): Promise<{ hasMore: boolean; newShouts: Shout[] }> => {
|
||||||
options.limit += 1
|
options.limit += 1
|
||||||
const newShouts = await apiClient.getMyFeed(options)
|
const newShouts = await apiClient.getMyFeed(options)
|
||||||
const hasMore = newShouts ?? newShouts.length === options.limit + 1
|
const hasMore = newShouts?.length === options.limit + 1
|
||||||
|
|
||||||
if (hasMore) {
|
if (hasMore) {
|
||||||
newShouts.splice(-1)
|
newShouts.splice(-1)
|
||||||
|
@ -165,7 +165,7 @@ export const loadShoutsSearch = async (
|
||||||
): Promise<{ hasMore: boolean; newShouts: Shout[] }> => {
|
): Promise<{ hasMore: boolean; newShouts: Shout[] }> => {
|
||||||
options.limit += 1
|
options.limit += 1
|
||||||
const newShouts = await apiClient.getShoutsSearch(options)
|
const newShouts = await apiClient.getShoutsSearch(options)
|
||||||
const hasMore = newShouts ?? newShouts.length === options.limit + 1
|
const hasMore = newShouts?.length === options.limit + 1
|
||||||
|
|
||||||
if (hasMore) {
|
if (hasMore) {
|
||||||
newShouts.splice(-1)
|
newShouts.splice(-1)
|
||||||
|
@ -193,7 +193,7 @@ export const loadTopMonthArticles = async (): Promise<void> => {
|
||||||
const after = Math.floor(daysago / 1000)
|
const after = Math.floor(daysago / 1000)
|
||||||
const options: LoadShoutsOptions = {
|
const options: LoadShoutsOptions = {
|
||||||
filters: {
|
filters: {
|
||||||
published: true,
|
featured: true,
|
||||||
after,
|
after,
|
||||||
},
|
},
|
||||||
order_by: 'likes_stat',
|
order_by: 'likes_stat',
|
||||||
|
@ -208,7 +208,7 @@ const TOP_ARTICLES_COUNT = 10
|
||||||
|
|
||||||
export const loadTopArticles = async (): Promise<void> => {
|
export const loadTopArticles = async (): Promise<void> => {
|
||||||
const options: LoadShoutsOptions = {
|
const options: LoadShoutsOptions = {
|
||||||
filters: { published: true },
|
filters: { featured: true },
|
||||||
order_by: 'likes_stat',
|
order_by: 'likes_stat',
|
||||||
limit: TOP_ARTICLES_COUNT,
|
limit: TOP_ARTICLES_COUNT,
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ export const resetSortedLayoutShouts = () => {
|
||||||
export const loadLayoutShoutsBy = async (options: LoadShoutsOptions): Promise<{ hasMore: boolean }> => {
|
export const loadLayoutShoutsBy = async (options: LoadShoutsOptions): Promise<{ hasMore: boolean }> => {
|
||||||
options.limit += 1
|
options.limit += 1
|
||||||
const newLayoutShouts = await apiClient.getShouts(options)
|
const newLayoutShouts = await apiClient.getShouts(options)
|
||||||
const hasMore = newLayoutShouts.length === options.limit + 1
|
const hasMore = newLayoutShouts?.length === options.limit + 1
|
||||||
|
|
||||||
if (hasMore) {
|
if (hasMore) {
|
||||||
newLayoutShouts.splice(-1)
|
newLayoutShouts.splice(-1)
|
||||||
|
|
|
@ -6,6 +6,10 @@ export const byCreated = (a: Shout | Reaction, b: Shout | Reaction) => {
|
||||||
return a?.created_at - b?.created_at
|
return a?.created_at - b?.created_at
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const byPublished = (a: Shout, b: Shout) => {
|
||||||
|
return a.published_at - b.published_at
|
||||||
|
}
|
||||||
|
|
||||||
export const byLength = (
|
export const byLength = (
|
||||||
a: (Shout | Reaction | Topic | Author)[],
|
a: (Shout | Reaction | Topic | Author)[],
|
||||||
b: (Shout | Reaction | Topic | Author)[],
|
b: (Shout | Reaction | Topic | Author)[],
|
||||||
|
|
Loading…
Reference in New Issue
Block a user