session-fx
This commit is contained in:
parent
62887f88c0
commit
9a55056b9d
|
@ -10,6 +10,7 @@ import { hideModal } from '../../../stores/ui'
|
||||||
import { PasswordField } from './PasswordField'
|
import { PasswordField } from './PasswordField'
|
||||||
|
|
||||||
import styles from './AuthModal.module.scss'
|
import styles from './AuthModal.module.scss'
|
||||||
|
import { useSession } from '../../../context/session'
|
||||||
|
|
||||||
type FormFields = {
|
type FormFields = {
|
||||||
password: string
|
password: string
|
||||||
|
@ -18,28 +19,31 @@ type FormFields = {
|
||||||
type ValidationErrors = Partial<Record<keyof FormFields, string | JSX.Element>>
|
type ValidationErrors = Partial<Record<keyof FormFields, string | JSX.Element>>
|
||||||
|
|
||||||
export const ChangePasswordForm = () => {
|
export const ChangePasswordForm = () => {
|
||||||
const { changeSearchParams } = useRouter<AuthModalSearchParams>()
|
const { searchParams, changeSearchParams } = useRouter<AuthModalSearchParams>()
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
|
const {
|
||||||
|
actions: { changePassword },
|
||||||
|
} = useSession()
|
||||||
const [isSubmitting, setIsSubmitting] = createSignal(false)
|
const [isSubmitting, setIsSubmitting] = createSignal(false)
|
||||||
const [validationErrors, setValidationErrors] = createSignal<ValidationErrors>({})
|
const [validationErrors, setValidationErrors] = createSignal<ValidationErrors>({})
|
||||||
const [newPassword, setNewPassword] = createSignal<string>()
|
const [newPassword, setNewPassword] = createSignal<string>()
|
||||||
const [passwordError, setPasswordError] = createSignal<string>()
|
const [passwordError, setPasswordError] = createSignal<string>()
|
||||||
const [isSuccess, setIsSuccess] = createSignal(false)
|
const [isSuccess, setIsSuccess] = createSignal(false)
|
||||||
|
|
||||||
const authFormRef: { current: HTMLFormElement } = { current: null }
|
const authFormRef: { current: HTMLFormElement } = { current: null }
|
||||||
|
|
||||||
const handleSubmit = async (event: Event) => {
|
const handleSubmit = async (event: Event) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
setIsSubmitting(true)
|
setIsSubmitting(true)
|
||||||
// Fake change password logic
|
if (newPassword()) {
|
||||||
console.log('!!! sent new password:', newPassword)
|
await changePassword(newPassword(), searchParams()?.token)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
setIsSubmitting(false)
|
setIsSubmitting(false)
|
||||||
setIsSuccess(true)
|
setIsSuccess(true)
|
||||||
}, 1000)
|
}, 1000)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handlePasswordInput = (value) => {
|
const handlePasswordInput = (value: string) => {
|
||||||
setNewPassword(value)
|
setNewPassword(value)
|
||||||
if (passwordError()) {
|
if (passwordError()) {
|
||||||
setValidationErrors((errors) => ({ ...errors, password: passwordError() }))
|
setValidationErrors((errors) => ({ ...errors, password: passwordError() }))
|
||||||
|
@ -93,6 +97,12 @@ export const ChangePasswordForm = () => {
|
||||||
<div class={styles.title}>{t('Password updated!')}</div>
|
<div class={styles.title}>{t('Password updated!')}</div>
|
||||||
<div class={styles.text}>{t('You can now login using your new password')}</div>
|
<div class={styles.text}>{t('You can now login using your new password')}</div>
|
||||||
<div>
|
<div>
|
||||||
|
<button
|
||||||
|
class={clsx('button', styles.submitButton)}
|
||||||
|
onClick={() => changeSearchParams({ mode: 'login' })}
|
||||||
|
>
|
||||||
|
{t('Enter')}
|
||||||
|
</button>
|
||||||
<button class={clsx('button', styles.submitButton)} onClick={() => hideModal()}>
|
<button class={clsx('button', styles.submitButton)} onClick={() => hideModal()}>
|
||||||
{t('Back to main page')}
|
{t('Back to main page')}
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import type { ConfirmEmailSearchParams } from './types'
|
import type { ConfirmEmailSearchParams } from './types'
|
||||||
|
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { createEffect, createMemo, createSignal, onMount, Show } from 'solid-js'
|
import { createEffect, createMemo, 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'
|
||||||
import { ApiError } from '../../../graphql/error'
|
|
||||||
import { useRouter } from '../../../stores/router'
|
import { useRouter } from '../../../stores/router'
|
||||||
import { hideModal } from '../../../stores/ui'
|
import { hideModal } from '../../../stores/ui'
|
||||||
|
|
||||||
|
@ -13,52 +12,21 @@ import styles from './AuthModal.module.scss'
|
||||||
|
|
||||||
export const EmailConfirm = () => {
|
export const EmailConfirm = () => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
|
const { searchParams } = useRouter<ConfirmEmailSearchParams>()
|
||||||
const {
|
const {
|
||||||
actions: { confirmEmail, loadSession, loadAuthor },
|
actions: { confirmEmail },
|
||||||
session,
|
session,
|
||||||
} = useSession()
|
} = useSession()
|
||||||
const [confirmedEmail, setConfirmedEmail] = createSignal<boolean>(false)
|
const [isTokenExpired, setIsTokenExpired] = createSignal(false) // TODO: handle expired token in context/session
|
||||||
|
const [isTokenInvalid, setIsTokenInvalid] = createSignal(false) // TODO: handle invalid token in context/session
|
||||||
|
|
||||||
const [isTokenExpired, setIsTokenExpired] = createSignal(false)
|
createEffect(async () => {
|
||||||
const [isTokenInvalid, setIsTokenInvalid] = createSignal(false)
|
const token = searchParams()?.access_token
|
||||||
const { searchParams, changeSearchParam } = useRouter<ConfirmEmailSearchParams>()
|
if (token) await confirmEmail({ token })
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
const token = searchParams().access_token
|
|
||||||
if (token) {
|
|
||||||
changeSearchParam({})
|
|
||||||
try {
|
|
||||||
await confirmEmail({ token })
|
|
||||||
await loadSession()
|
|
||||||
await loadAuthor()
|
|
||||||
} catch (error) {
|
|
||||||
// TODO: adapt this code to authorizer
|
|
||||||
if (error instanceof ApiError) {
|
|
||||||
if (error.code === 'token_expired') {
|
|
||||||
setIsTokenExpired(true)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error.code === 'token_invalid') {
|
|
||||||
setIsTokenInvalid(true)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
const confirmed = session()?.user?.email_verified
|
|
||||||
if (confirmed) {
|
|
||||||
console.debug(`[EmailConfirm] email successfully verified`)
|
|
||||||
setConfirmedEmail(confirmed)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const email = createMemo(() => session()?.user?.email)
|
const email = createMemo(() => session()?.user?.email)
|
||||||
|
const confirmedEmail = createMemo(() => session()?.user?.email_verified)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -5,7 +5,7 @@ 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 { ApiError } from '../../../graphql/error'
|
||||||
import { useRouter } from '../../../stores/router'
|
import { useRouter } from '../../../stores/router'
|
||||||
import { validateEmail } from '../../../utils/validateEmail'
|
import { validateEmail } from '../../../utils/validateEmail'
|
||||||
|
|
||||||
|
@ -33,17 +33,13 @@ export const ForgotPasswordForm = () => {
|
||||||
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 [isUserNotFount, 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('')
|
setSubmitError('')
|
||||||
setIsUserNotFound(false)
|
setIsUserNotFound(false)
|
||||||
|
|
||||||
const newValidationErrors: ValidationErrors = {}
|
const newValidationErrors: ValidationErrors = {}
|
||||||
|
|
||||||
if (!email()) {
|
if (!email()) {
|
||||||
|
@ -53,7 +49,6 @@ export const ForgotPasswordForm = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
setValidationErrors(newValidationErrors)
|
setValidationErrors(newValidationErrors)
|
||||||
|
|
||||||
const isValid = Object.keys(newValidationErrors).length === 0
|
const isValid = Object.keys(newValidationErrors).length === 0
|
||||||
|
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
|
@ -68,18 +63,17 @@ export const ForgotPasswordForm = () => {
|
||||||
try {
|
try {
|
||||||
const response = await authorizer().forgotPassword({
|
const response = await authorizer().forgotPassword({
|
||||||
email: email(),
|
email: email(),
|
||||||
redirect_uri: window.location.href + '&success=1', // FIXME: redirect to success page accepting confirmation code
|
redirect_uri: window.location.origin,
|
||||||
})
|
})
|
||||||
if (response) {
|
console.debug('[ForgotPasswordForm] authorizer response: ', response)
|
||||||
console.debug('[ForgotPasswordForm]', response)
|
if (response && response.message) setMessage(response.message)
|
||||||
if (response.message) setMessage(response.message)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof ApiError && error.code === 'user_not_found') {
|
console.error(error)
|
||||||
|
if (error?.code === 'user_not_found') {
|
||||||
setIsUserNotFound(true)
|
setIsUserNotFound(true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setSubmitError(error.message)
|
setSubmitError(error?.message)
|
||||||
} finally {
|
} finally {
|
||||||
setIsSubmitting(false)
|
setIsSubmitting(false)
|
||||||
}
|
}
|
||||||
|
@ -92,17 +86,17 @@ export const ForgotPasswordForm = () => {
|
||||||
ref={(el) => (authFormRef.current = el)}
|
ref={(el) => (authFormRef.current = el)}
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<h4>{t('Forgot password?')}</h4>
|
<h4>{t('Restore password')}</h4>
|
||||||
<div class={styles.authSubtitle}>
|
<div class={styles.authSubtitle}>
|
||||||
{t(message()) || t('Everything is ok, please give us your email address')}
|
{t(message()) || t('Everything is ok, please give us your email address')}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class={clsx('pretty-form__item', {
|
class={clsx('pretty-form__item', {
|
||||||
'pretty-form__item--error': validationErrors().email,
|
'pretty-form__item--error': validationErrors().email,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
|
disabled={Boolean(message())}
|
||||||
id="email"
|
id="email"
|
||||||
name="email"
|
name="email"
|
||||||
autocomplete="email"
|
autocomplete="email"
|
||||||
|
@ -144,7 +138,11 @@ export const ForgotPasswordForm = () => {
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<button class={clsx('button', styles.submitButton)} disabled={isSubmitting()} type="submit">
|
<button
|
||||||
|
class={clsx('button', styles.submitButton)}
|
||||||
|
disabled={isSubmitting() || Boolean(message())}
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
{isSubmitting() ? '...' : t('Restore password')}
|
{isSubmitting() ? '...' : t('Restore password')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -17,6 +17,7 @@ import { email, setEmail } from './sharedLogic'
|
||||||
import { SocialProviders } from './SocialProviders'
|
import { SocialProviders } from './SocialProviders'
|
||||||
|
|
||||||
import styles from './AuthModal.module.scss'
|
import styles from './AuthModal.module.scss'
|
||||||
|
import { VerifyEmailInput } from '@authorizerdev/authorizer-js'
|
||||||
|
|
||||||
type FormFields = {
|
type FormFields = {
|
||||||
email: string
|
email: string
|
||||||
|
@ -65,11 +66,12 @@ export const LoginForm = () => {
|
||||||
setIsLinkSent(true)
|
setIsLinkSent(true)
|
||||||
setIsEmailNotConfirmed(false)
|
setIsEmailNotConfirmed(false)
|
||||||
setSubmitError('')
|
setSubmitError('')
|
||||||
const {
|
changeSearchParams({ mode: 'forgot-password' }) // NOTE: temporary solition
|
||||||
actions: { authorizer, getToken },
|
/* FIXME:
|
||||||
} = useSession()
|
const { actions: { authorizer } } = useSession()
|
||||||
const result = await authorizer().verifyEmail({ token: getToken() })
|
const result = await authorizer().verifyEmail({ token })
|
||||||
if (!result) setSubmitError('cant sign send link') // TODO:
|
if (!result) setSubmitError('cant sign send link')
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSubmit = async (event: Event) => {
|
const handleSubmit = async (event: Event) => {
|
||||||
|
@ -110,6 +112,7 @@ export const LoginForm = () => {
|
||||||
|
|
||||||
showSnackbar({ body: t('Welcome!') })
|
showSnackbar({ body: t('Welcome!') })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
if (error instanceof ApiError) {
|
if (error instanceof ApiError) {
|
||||||
if (error.code === 'email_not_confirmed') {
|
if (error.code === 'email_not_confirmed') {
|
||||||
setSubmitError(t('Please, confirm email'))
|
setSubmitError(t('Please, confirm email'))
|
||||||
|
@ -184,16 +187,6 @@ export const LoginForm = () => {
|
||||||
>
|
>
|
||||||
{t('Forgot password?')}
|
{t('Forgot password?')}
|
||||||
</span>
|
</span>
|
||||||
<span
|
|
||||||
class="link"
|
|
||||||
onClick={() =>
|
|
||||||
changeSearchParams({
|
|
||||||
mode: 'change-password',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{t('Change password')}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ 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 { 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'
|
||||||
|
@ -36,7 +36,7 @@ export const RegisterForm = () => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
const { emailChecks } = useEmailChecks()
|
const { emailChecks } = useEmailChecks()
|
||||||
const {
|
const {
|
||||||
actions: { authorizer },
|
actions: { signUp },
|
||||||
} = useSession()
|
} = useSession()
|
||||||
const [submitError, setSubmitError] = createSignal('')
|
const [submitError, setSubmitError] = createSignal('')
|
||||||
const [fullName, setFullName] = createSignal('')
|
const [fullName, setFullName] = createSignal('')
|
||||||
|
@ -105,19 +105,20 @@ export const RegisterForm = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsSubmitting(true)
|
setIsSubmitting(true)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await authorizer().signup({
|
const opts = {
|
||||||
given_name: cleanName,
|
given_name: cleanName,
|
||||||
email: cleanEmail,
|
email: cleanEmail,
|
||||||
password: password(),
|
password: password(),
|
||||||
confirm_password: password(),
|
confirm_password: password(),
|
||||||
redirect_uri: window.location.origin,
|
redirect_uri: window.location.origin,
|
||||||
})
|
}
|
||||||
|
await signUp(opts)
|
||||||
|
|
||||||
setIsSuccess(true)
|
setIsSuccess(true)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof ApiError && error.code === 'user_already_exists') {
|
console.error(error)
|
||||||
|
if (error?.code === 'user_already_exists') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ export type AuthModalSource =
|
||||||
export type AuthModalSearchParams = {
|
export type AuthModalSearchParams = {
|
||||||
mode: AuthModalMode
|
mode: AuthModalMode
|
||||||
source?: AuthModalSource
|
source?: AuthModalSource
|
||||||
|
token?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ConfirmEmailSearchParams = {
|
export type ConfirmEmailSearchParams = {
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
.center {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
height: 420px;
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
import './Confirmed.scss'
|
|
||||||
import { onMount } from 'solid-js'
|
|
||||||
|
|
||||||
import { useLocalize } from '../../context/localize'
|
|
||||||
|
|
||||||
export const Confirmed = (props: { token?: string }) => {
|
|
||||||
const { t } = useLocalize()
|
|
||||||
onMount(() => {
|
|
||||||
const token = props.token ?? document.cookie.split(';').at(0).replace('token=', '')
|
|
||||||
window.addEventListener('mousemove', () => window.close())
|
|
||||||
window.addEventListener('keydown', () => window.close())
|
|
||||||
window.addEventListener('click', () => window.close())
|
|
||||||
localStorage.setItem('token', token)
|
|
||||||
})
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div class="center">{t('You was successfully authorized')}</div>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -32,7 +32,7 @@ const MD_WIDTH_BREAKPOINT = 992
|
||||||
export const HeaderAuth = (props: Props) => {
|
export const HeaderAuth = (props: Props) => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
const { page } = useRouter()
|
const { page } = useRouter()
|
||||||
const { author, isAuthenticated, isSessionLoaded } = useSession()
|
const { session, author, isAuthenticated, isSessionLoaded } = useSession()
|
||||||
const {
|
const {
|
||||||
unreadNotificationsCount,
|
unreadNotificationsCount,
|
||||||
actions: { showNotificationsPanel },
|
actions: { showNotificationsPanel },
|
||||||
|
@ -139,12 +139,12 @@ export const HeaderAuth = (props: Props) => {
|
||||||
<div class={styles.button}>
|
<div class={styles.button}>
|
||||||
<Icon
|
<Icon
|
||||||
name="bell-white"
|
name="bell-white"
|
||||||
counter={isAuthenticated() ? unreadNotificationsCount() : 1}
|
counter={session() ? unreadNotificationsCount() || 0 : 1}
|
||||||
class={styles.icon}
|
class={styles.icon}
|
||||||
/>
|
/>
|
||||||
<Icon
|
<Icon
|
||||||
name="bell-white-hover"
|
name="bell-white-hover"
|
||||||
counter={isAuthenticated() ? unreadNotificationsCount() : 1}
|
counter={session() ? unreadNotificationsCount() || 0 : 1}
|
||||||
class={clsx(styles.icon, styles.iconHover)}
|
class={clsx(styles.icon, styles.iconHover)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -67,10 +67,11 @@ export const HomeView = (props: Props) => {
|
||||||
setIsLoadMoreButtonVisible(hasMore)
|
setIsLoadMoreButtonVisible(hasMore)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { topic, shouts } = await apiClient.getRandomTopicShouts(RANDOM_TOPIC_SHOUTS_COUNT)
|
const result = await apiClient.getRandomTopicShouts(RANDOM_TOPIC_SHOUTS_COUNT)
|
||||||
|
if (!result) console.warn('[apiClient.getRandomTopicShouts] failed')
|
||||||
batch(() => {
|
batch(() => {
|
||||||
setRandomTopic(topic)
|
if (result?.topic) setRandomTopic(result.topic)
|
||||||
setRandomTopicArticles(shouts)
|
if (result?.shouts) setRandomTopicArticles(result.shouts)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type { JSX } from 'solid-js'
|
import type { JSX } from 'solid-js'
|
||||||
|
|
||||||
import { Link } from '@solidjs/meta'
|
import { Link } from '@solidjs/meta'
|
||||||
import { splitProps } from 'solid-js'
|
import { createEffect, splitProps } from 'solid-js'
|
||||||
|
|
||||||
import { getImageUrl } from '../../../utils/getImageUrl'
|
import { getImageUrl } from '../../../utils/getImageUrl'
|
||||||
|
|
||||||
|
@ -21,10 +21,9 @@ export const Image = (props: Props) => {
|
||||||
`${getImageUrl(local.src, { width: others.width * pixelDensity })} ${pixelDensity}x`,
|
`${getImageUrl(local.src, { width: others.width * pixelDensity })} ${pixelDensity}x`,
|
||||||
)
|
)
|
||||||
.join(', ')
|
.join(', ')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Link rel="preload" as="image" imagesrcset={imageSrcSet} />
|
<Link rel="preload" as="image" imagesrcset={imageSrcSet} href={imageUrl} />
|
||||||
<img src={imageUrl} alt={local.alt} srcSet={imageSrcSet} {...others} />
|
<img src={imageUrl} alt={local.alt} srcSet={imageSrcSet} {...others} />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
@ -10,10 +10,10 @@ type ShowIfAuthenticatedProps = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ShowIfAuthenticated = (props: ShowIfAuthenticatedProps) => {
|
export const ShowIfAuthenticated = (props: ShowIfAuthenticatedProps) => {
|
||||||
const { isAuthenticated } = useSession()
|
const { author } = useSession()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Show when={isAuthenticated()} fallback={props.fallback}>
|
<Show when={author()} fallback={props.fallback}>
|
||||||
{props.children}
|
{props.children}
|
||||||
</Show>
|
</Show>
|
||||||
)
|
)
|
||||||
|
|
|
@ -9,8 +9,8 @@ const RECONNECT_TIMES = 2
|
||||||
|
|
||||||
export interface SSEMessage {
|
export interface SSEMessage {
|
||||||
id: string
|
id: string
|
||||||
entity: string
|
entity: string // follower | shout | reaction
|
||||||
action: string
|
action: string // create | delete | update | join | follow | seen
|
||||||
payload: any // Author | Shout | Reaction | Message
|
payload: any // Author | Shout | Reaction | Message
|
||||||
created_at?: number // unixtime x1000
|
created_at?: number // unixtime x1000
|
||||||
seen?: boolean
|
seen?: boolean
|
||||||
|
@ -29,9 +29,7 @@ export const ConnectProvider = (props: { children: JSX.Element }) => {
|
||||||
const [messageHandlers, setHandlers] = createSignal<Array<MessageHandler>>([])
|
const [messageHandlers, setHandlers] = createSignal<Array<MessageHandler>>([])
|
||||||
// const [messages, setMessages] = createSignal<Array<SSEMessage>>([]);
|
// const [messages, setMessages] = createSignal<Array<SSEMessage>>([]);
|
||||||
const [connected, setConnected] = createSignal(false)
|
const [connected, setConnected] = createSignal(false)
|
||||||
const {
|
const { session } = useSession()
|
||||||
actions: { getToken },
|
|
||||||
} = useSession()
|
|
||||||
|
|
||||||
const addHandler = (handler: MessageHandler) => {
|
const addHandler = (handler: MessageHandler) => {
|
||||||
setHandlers((hhh) => [...hhh, handler])
|
setHandlers((hhh) => [...hhh, handler])
|
||||||
|
@ -39,8 +37,9 @@ export const ConnectProvider = (props: { children: JSX.Element }) => {
|
||||||
|
|
||||||
const [retried, setRetried] = createSignal<number>(0)
|
const [retried, setRetried] = createSignal<number>(0)
|
||||||
createEffect(async () => {
|
createEffect(async () => {
|
||||||
const token = getToken()
|
const token = session()?.access_token
|
||||||
if (token && !connected()) {
|
if (token && !connected()) {
|
||||||
|
console.info('[context.connect] init SSE connection')
|
||||||
await fetchEventSource('https://connect.discours.io', {
|
await fetchEventSource('https://connect.discours.io', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
AuthToken,
|
AuthToken,
|
||||||
Authorizer,
|
Authorizer,
|
||||||
ConfigType,
|
ConfigType,
|
||||||
|
SignupInput,
|
||||||
} from '@authorizerdev/authorizer-js'
|
} from '@authorizerdev/authorizer-js'
|
||||||
import {
|
import {
|
||||||
createContext,
|
createContext,
|
||||||
|
@ -29,9 +30,9 @@ import { showModal } from '../stores/ui'
|
||||||
import { useLocalize } from './localize'
|
import { useLocalize } from './localize'
|
||||||
import { useSnackbar } from './snackbar'
|
import { useSnackbar } from './snackbar'
|
||||||
|
|
||||||
const config: ConfigType = {
|
const defaultConfig: ConfigType = {
|
||||||
authorizerURL: 'https://auth.discours.io',
|
authorizerURL: 'https://auth.discours.io',
|
||||||
redirectURL: 'https://discoursio-webapp.vercel.app/?modal=auth',
|
redirectURL: 'https://discoursio-webapp.vercel.app',
|
||||||
clientID: '9c113377-5eea-4c89-98e1-69302462fc08', // FIXME: use env?
|
clientID: '9c113377-5eea-4c89-98e1-69302462fc08', // FIXME: use env?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,10 +42,9 @@ export type SessionContextType = {
|
||||||
author: Resource<Author | null>
|
author: Resource<Author | null>
|
||||||
isSessionLoaded: Accessor<boolean>
|
isSessionLoaded: Accessor<boolean>
|
||||||
subscriptions: Accessor<Result>
|
subscriptions: Accessor<Result>
|
||||||
isAuthenticated: Accessor<boolean>
|
|
||||||
isAuthWithCallback: Accessor<() => void>
|
isAuthWithCallback: Accessor<() => void>
|
||||||
|
isAuthenticated: Accessor<boolean>
|
||||||
actions: {
|
actions: {
|
||||||
getToken: () => string
|
|
||||||
loadSession: () => AuthToken | Promise<AuthToken>
|
loadSession: () => AuthToken | Promise<AuthToken>
|
||||||
setSession: (token: AuthToken | null) => void // setSession
|
setSession: (token: AuthToken | null) => void // setSession
|
||||||
loadAuthor: (info?: unknown) => Author | Promise<Author>
|
loadAuthor: (info?: unknown) => Author | Promise<Author>
|
||||||
|
@ -54,9 +54,11 @@ export type SessionContextType = {
|
||||||
callback: (() => Promise<void>) | (() => void),
|
callback: (() => Promise<void>) | (() => void),
|
||||||
modalSource: AuthModalSource,
|
modalSource: AuthModalSource,
|
||||||
) => void
|
) => void
|
||||||
|
signUp: (params: SignupInput) => Promise<AuthToken | void>
|
||||||
signIn: (params: LoginInput) => Promise<void>
|
signIn: (params: LoginInput) => Promise<void>
|
||||||
signOut: () => Promise<void>
|
signOut: () => Promise<void>
|
||||||
confirmEmail: (input: VerifyEmailInput) => Promise<void> // email confirm callback is in auth.discours.io
|
changePassword: (password: string, token: string) => void
|
||||||
|
confirmEmail: (input: VerifyEmailInput) => Promise<AuthToken | void> // email confirm callback is in auth.discours.io
|
||||||
setIsSessionLoaded: (loaded: boolean) => void
|
setIsSessionLoaded: (loaded: boolean) => void
|
||||||
authorizer: () => Authorizer
|
authorizer: () => Authorizer
|
||||||
}
|
}
|
||||||
|
@ -81,75 +83,40 @@ export const SessionProvider = (props: {
|
||||||
const {
|
const {
|
||||||
actions: { showSnackbar },
|
actions: { showSnackbar },
|
||||||
} = useSnackbar()
|
} = useSnackbar()
|
||||||
const { searchParams, changeSearchParam } = useRouter()
|
const { searchParams, changeSearchParams } = useRouter()
|
||||||
|
|
||||||
|
// handle callback's redirect_uri
|
||||||
|
createEffect(async () => {
|
||||||
|
// TODO: handle oauth here too
|
||||||
|
const token = searchParams()?.token
|
||||||
|
const access_token = searchParams()?.access_token
|
||||||
|
if (token) {
|
||||||
|
changeSearchParams({
|
||||||
|
mode: 'change-password',
|
||||||
|
modal: 'auth',
|
||||||
|
})
|
||||||
|
} else if (access_token) {
|
||||||
|
changeSearchParams({
|
||||||
|
mode: 'confirm-email',
|
||||||
|
modal: 'auth',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// load
|
||||||
|
|
||||||
|
const [configuration, setConfig] = createSignal<ConfigType>(defaultConfig)
|
||||||
|
const authorizer = createMemo(() => new Authorizer(defaultConfig))
|
||||||
const [isSessionLoaded, setIsSessionLoaded] = createSignal(false)
|
const [isSessionLoaded, setIsSessionLoaded] = createSignal(false)
|
||||||
const [subscriptions, setSubscriptions] = createSignal<Result>(EMPTY_SUBSCRIPTIONS)
|
const [session, { refetch: loadSession, mutate: setSession }] = createResource<AuthToken>(
|
||||||
|
|
||||||
const getSession = async (): Promise<AuthToken> => {
|
|
||||||
try {
|
|
||||||
const tkn = getToken()
|
|
||||||
// console.debug('[context.session] token before:', tkn)
|
|
||||||
const authResult = await authorizer().getSession({
|
|
||||||
Authorization: tkn,
|
|
||||||
})
|
|
||||||
if (authResult?.access_token) {
|
|
||||||
setSession(authResult)
|
|
||||||
// console.debug('[context.session] token after:', authResult.access_token)
|
|
||||||
await loadSubscriptions()
|
|
||||||
return authResult
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[context.session] getSession error:', error)
|
|
||||||
setSession(null)
|
|
||||||
return null
|
|
||||||
} finally {
|
|
||||||
setTimeout(() => {
|
|
||||||
setIsSessionLoaded(true)
|
|
||||||
}, 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const [session, { refetch: loadSession, mutate: setSession }] = createResource<AuthToken>(getSession, {
|
|
||||||
ssrLoadFrom: 'initial',
|
|
||||||
initialValue: null,
|
|
||||||
})
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
// detect confirm redirect
|
|
||||||
const params = searchParams()
|
|
||||||
if (params?.access_token) {
|
|
||||||
console.debug('[context.session] access token presented, changing search params')
|
|
||||||
changeSearchParam({ modal: 'auth', mode: 'confirm-email', access_token: params?.access_token })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
createEffect(() => {
|
|
||||||
const token = getToken()
|
|
||||||
if (!inboxClient.private && token) {
|
|
||||||
apiClient.connect(token)
|
|
||||||
notifierClient.connect(token)
|
|
||||||
inboxClient.connect(token)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const loadSubscriptions = async (): Promise<void> => {
|
|
||||||
if (apiClient.private) {
|
|
||||||
const result = await apiClient.getMySubscriptions()
|
|
||||||
if (result) {
|
|
||||||
setSubscriptions(result)
|
|
||||||
} else {
|
|
||||||
setSubscriptions(EMPTY_SUBSCRIPTIONS)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const [author, { refetch: loadAuthor, mutate: setAuthor }] = createResource<Author | null>(
|
|
||||||
async () => {
|
async () => {
|
||||||
const u = session()?.user
|
try {
|
||||||
if (u) {
|
console.info('[context.session] loading session')
|
||||||
return (await apiClient.getAuthorId({ user: u.id })) ?? null
|
return await authorizer().getSession()
|
||||||
}
|
} catch (_) {
|
||||||
|
console.info('[context.session] cannot refresh session')
|
||||||
return null
|
return null
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ssrLoadFrom: 'initial',
|
ssrLoadFrom: 'initial',
|
||||||
|
@ -157,29 +124,71 @@ export const SessionProvider = (props: {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
const isAuthenticated = createMemo(() => Boolean(session()?.user))
|
const [author, { refetch: loadAuthor, mutate: setAuthor }] = createResource<Author | null>(
|
||||||
|
async () => {
|
||||||
const signIn = async (params: LoginInput) => {
|
const u = session()?.user
|
||||||
const authResult: AuthToken | void = await authorizer().login(params)
|
return u ? (await apiClient.getAuthorId({ user: u.id })) || null : null
|
||||||
|
},
|
||||||
if (authResult && authResult.access_token) {
|
{
|
||||||
setSession(authResult)
|
ssrLoadFrom: 'initial',
|
||||||
await loadSubscriptions()
|
initialValue: null,
|
||||||
console.debug('[context.session] signed in')
|
},
|
||||||
} else {
|
|
||||||
console.info((authResult as AuthToken).message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const authorizer = createMemo(
|
|
||||||
() =>
|
|
||||||
new Authorizer({
|
|
||||||
authorizerURL: config.authorizerURL,
|
|
||||||
redirectURL: config.redirectURL,
|
|
||||||
clientID: config.clientID,
|
|
||||||
}),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const [subscriptions, setSubscriptions] = createSignal<Result>(EMPTY_SUBSCRIPTIONS)
|
||||||
|
const loadSubscriptions = async (): Promise<void> => {
|
||||||
|
const result = await apiClient.getMySubscriptions()
|
||||||
|
setSubscriptions(result || EMPTY_SUBSCRIPTIONS)
|
||||||
|
}
|
||||||
|
|
||||||
|
// session postload effect
|
||||||
|
createEffect(async () => {
|
||||||
|
if (session()) {
|
||||||
|
const token = session()?.access_token
|
||||||
|
if (token) {
|
||||||
|
console.log('[context.session] token observer got token', token)
|
||||||
|
if (!inboxClient.private) {
|
||||||
|
apiClient.connect(token)
|
||||||
|
notifierClient.connect(token)
|
||||||
|
inboxClient.connect(token)
|
||||||
|
}
|
||||||
|
if (!author()) {
|
||||||
|
const a = await loadAuthor()
|
||||||
|
await loadSubscriptions()
|
||||||
|
if (a) {
|
||||||
|
console.log('[context.session] author profile and subs loaded', author())
|
||||||
|
} else {
|
||||||
|
console.warn('[context.session] author is not loaded')
|
||||||
|
}
|
||||||
|
setIsSessionLoaded(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('[context.session] setting session null')
|
||||||
|
if (session() === null && author() !== null) {
|
||||||
|
setIsSessionLoaded(true)
|
||||||
|
setAuthor(null)
|
||||||
|
setSubscriptions(EMPTY_SUBSCRIPTIONS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// initial effect
|
||||||
|
onMount(async () => {
|
||||||
|
const metaRes = await authorizer().getMetaData()
|
||||||
|
setConfig({ ...defaultConfig, ...metaRes, redirectURL: window.location.origin })
|
||||||
|
let s
|
||||||
|
try {
|
||||||
|
s = await loadSession()
|
||||||
|
} catch (e) {}
|
||||||
|
if (!s) {
|
||||||
|
setIsSessionLoaded(true)
|
||||||
|
setSession(null)
|
||||||
|
setAuthor(null)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// callback state updater
|
||||||
createEffect(
|
createEffect(
|
||||||
on(
|
on(
|
||||||
() => props.onStateChangeCallback,
|
() => props.onStateChangeCallback,
|
||||||
|
@ -190,56 +199,57 @@ export const SessionProvider = (props: {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
const [configuration, setConfig] = createSignal<ConfigType>(config)
|
// require auth wrapper
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
setIsSessionLoaded(false)
|
|
||||||
console.log('[context.session] loading...')
|
|
||||||
const metaRes = await authorizer().getMetaData()
|
|
||||||
setConfig({ ...config, ...metaRes, redirectURL: window.location.origin + '/?modal=auth' })
|
|
||||||
console.log('[context.session] refreshing session...')
|
|
||||||
const s = await getSession()
|
|
||||||
console.debug('[context.session] session:', s)
|
|
||||||
console.log('[context.session] loading author...')
|
|
||||||
const a = await loadAuthor()
|
|
||||||
console.debug('[context.session] author:', a)
|
|
||||||
setIsSessionLoaded(true)
|
|
||||||
console.log('[context.session] loaded')
|
|
||||||
})
|
|
||||||
|
|
||||||
const [isAuthWithCallback, setIsAuthWithCallback] = createSignal<() => void>()
|
const [isAuthWithCallback, setIsAuthWithCallback] = createSignal<() => void>()
|
||||||
const requireAuthentication = async (callback: () => void, modalSource: AuthModalSource) => {
|
const requireAuthentication = async (callback: () => void, modalSource: AuthModalSource) => {
|
||||||
setIsAuthWithCallback(() => callback)
|
setIsAuthWithCallback(() => callback)
|
||||||
|
|
||||||
const userdata = await authorizer().getProfile()
|
await loadSession()
|
||||||
if (userdata) setSession({ ...session(), user: userdata })
|
|
||||||
|
|
||||||
if (!isAuthenticated()) {
|
if (!session()) {
|
||||||
showModal('auth', modalSource)
|
showModal('auth', modalSource)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// authorizer api proxy methods
|
||||||
|
|
||||||
|
const signUp = async (params) => {
|
||||||
|
const authResult: void | AuthToken = await authorizer().signup({
|
||||||
|
...params,
|
||||||
|
confirm_password: params.password,
|
||||||
|
})
|
||||||
|
if (authResult) setSession(authResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
const signIn = async (params: LoginInput) => {
|
||||||
|
const authResult: AuthToken | void = await authorizer().login(params)
|
||||||
|
if (authResult) setSession(authResult)
|
||||||
|
}
|
||||||
|
|
||||||
const signOut = async () => {
|
const signOut = async () => {
|
||||||
await authorizer().logout()
|
await authorizer().logout()
|
||||||
setSession(null)
|
setSession(null)
|
||||||
setSubscriptions(EMPTY_SUBSCRIPTIONS)
|
|
||||||
showSnackbar({ body: t("You've successfully logged out") })
|
showSnackbar({ body: t("You've successfully logged out") })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const changePassword = async (password: string, token: string) => {
|
||||||
|
const resp = await authorizer().resetPassword({ password, token, confirm_password: password })
|
||||||
|
console.debug('[context.session] change password response:', resp)
|
||||||
|
}
|
||||||
|
|
||||||
const confirmEmail = async (input: VerifyEmailInput) => {
|
const confirmEmail = async (input: VerifyEmailInput) => {
|
||||||
console.debug(`[context.session] calling authorizer's verify email with`, input)
|
console.debug(`[context.session] calling authorizer's verify email with`, input)
|
||||||
const at: void | AuthToken = await authorizer().verifyEmail(input)
|
const at: void | AuthToken = await authorizer().verifyEmail(input)
|
||||||
if (at) setSession(at)
|
if (at) setSession(at)
|
||||||
console.log(`[context.session] confirmEmail got result ${at}`)
|
return at
|
||||||
}
|
}
|
||||||
|
|
||||||
const getToken = createMemo(() => session()?.access_token)
|
const isAuthenticated = createMemo(() => Boolean(author()))
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
getToken,
|
|
||||||
loadSession,
|
loadSession,
|
||||||
loadSubscriptions,
|
loadSubscriptions,
|
||||||
requireAuthentication,
|
requireAuthentication,
|
||||||
|
signUp,
|
||||||
signIn,
|
signIn,
|
||||||
signOut,
|
signOut,
|
||||||
confirmEmail,
|
confirmEmail,
|
||||||
|
@ -248,16 +258,17 @@ export const SessionProvider = (props: {
|
||||||
setAuthor,
|
setAuthor,
|
||||||
authorizer,
|
authorizer,
|
||||||
loadAuthor,
|
loadAuthor,
|
||||||
|
changePassword,
|
||||||
}
|
}
|
||||||
const value: SessionContextType = {
|
const value: SessionContextType = {
|
||||||
config: configuration(),
|
config: configuration(),
|
||||||
session,
|
session,
|
||||||
subscriptions,
|
subscriptions,
|
||||||
isSessionLoaded,
|
isSessionLoaded,
|
||||||
isAuthenticated,
|
|
||||||
author,
|
author,
|
||||||
actions,
|
actions,
|
||||||
isAuthWithCallback,
|
isAuthWithCallback,
|
||||||
|
isAuthenticated,
|
||||||
}
|
}
|
||||||
|
|
||||||
return <SessionContext.Provider value={value}>{props.children}</SessionContext.Provider>
|
return <SessionContext.Provider value={value}>{props.children}</SessionContext.Provider>
|
||||||
|
|
|
@ -53,39 +53,28 @@ export const apiClient = {
|
||||||
|
|
||||||
getRandomTopShouts: async (params: QueryLoad_Shouts_Random_TopArgs) => {
|
getRandomTopShouts: async (params: QueryLoad_Shouts_Random_TopArgs) => {
|
||||||
const response = await publicGraphQLClient.query(loadShoutsTopRandom, params).toPromise()
|
const response = await publicGraphQLClient.query(loadShoutsTopRandom, params).toPromise()
|
||||||
if (!response.data) {
|
if (!response.data) console.error('[graphql.core] load_shouts_random_top failed', response)
|
||||||
console.error('[graphql.core] getRandomTopShouts error', response.error)
|
|
||||||
}
|
|
||||||
return response.data.load_shouts_random_top
|
return response.data.load_shouts_random_top
|
||||||
},
|
},
|
||||||
|
|
||||||
getUnratedShouts: async (limit = 50, offset = 0) => {
|
getUnratedShouts: async (limit = 50, offset = 0) => {
|
||||||
const response = await apiClient.private.query(loadShoutsUnrated, { limit, offset }).toPromise()
|
const response = await apiClient.private.query(loadShoutsUnrated, { limit, offset }).toPromise()
|
||||||
|
if (!response.data) console.error('[graphql.core] load_shouts_unrated', response)
|
||||||
if (!response.data) {
|
|
||||||
console.error('[graphql.core] getUnratedShouts error', response.error)
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.data.load_shouts_unrated
|
return response.data.load_shouts_unrated
|
||||||
},
|
},
|
||||||
|
|
||||||
getRandomTopics: async ({ amount }: { amount: number }) => {
|
getRandomTopics: async ({ amount }: { amount: number }) => {
|
||||||
const response = await publicGraphQLClient.query(topicsRandomQuery, { amount }).toPromise()
|
const response = await publicGraphQLClient.query(topicsRandomQuery, { amount }).toPromise()
|
||||||
|
if (!response.data) console.error('[graphql.client.core] get_topics_random failed', response)
|
||||||
if (!response.data) {
|
|
||||||
console.error('[graphql.client.core] getRandomTopics', response.error)
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.data.get_topics_random
|
return response.data.get_topics_random
|
||||||
},
|
},
|
||||||
|
|
||||||
getRandomTopicShouts: async (limit: number): Promise<{ topic: Topic; shouts: Shout[] }> => {
|
getRandomTopicShouts: async (limit: number): Promise<{ topic: Topic; shouts: Shout[] }> => {
|
||||||
const resp = await publicGraphQLClient.query(articlesLoadRandomTopic, { limit }).toPromise()
|
const resp = await publicGraphQLClient.query(articlesLoadRandomTopic, { limit }).toPromise()
|
||||||
|
if (!resp.data) console.error('[graphql.client.core] load_shouts_random_topic', resp)
|
||||||
if (resp.error) {
|
|
||||||
console.error(resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp.data.load_random_topics_shouts
|
return resp.data.load_random_topics_shouts
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -100,16 +89,14 @@ export const apiClient = {
|
||||||
|
|
||||||
getAllTopics: async () => {
|
getAllTopics: async () => {
|
||||||
const response = await publicGraphQLClient.query(topicsAll, {}).toPromise()
|
const response = await publicGraphQLClient.query(topicsAll, {}).toPromise()
|
||||||
if (response.error) {
|
if (!response.data) console.error('[graphql.client.core] get_topics_all', response)
|
||||||
console.debug('[graphql.client.core] get_topics_all', response.error)
|
|
||||||
}
|
|
||||||
return response.data.get_topics_all
|
return response.data.get_topics_all
|
||||||
},
|
},
|
||||||
getAllAuthors: async (limit: number = 50, offset: number = 0) => {
|
getAllAuthors: async (limit: number = 50, offset: number = 0) => {
|
||||||
const response = await publicGraphQLClient.query(authorsAll, { limit, offset }).toPromise()
|
const response = await publicGraphQLClient.query(authorsAll, { limit, offset }).toPromise()
|
||||||
if (response.error) {
|
if (!response.data) console.error('[graphql.client.core] load_authors_all', response)
|
||||||
console.debug('[graphql.client.core] load_authors_all', response.error)
|
|
||||||
}
|
|
||||||
return response.data.load_authors_all
|
return response.data.load_authors_all
|
||||||
},
|
},
|
||||||
getAuthor: async (params: { slug?: string; author_id?: number }): Promise<Author> => {
|
getAuthor: async (params: { slug?: string; author_id?: number }): Promise<Author> => {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { gql } from '@urql/core'
|
import { gql } from '@urql/core'
|
||||||
|
|
||||||
export default gql`
|
export default gql`
|
||||||
query LoadRandomTopShoutsQuery($params: LoadRandomTopShoutsParams) {
|
query LoadRandomTopShoutsQuery($options: LoadShoutsOptions) {
|
||||||
load_shouts_random_top(params: $params) {
|
load_shouts_random_top(options: $options) {
|
||||||
id
|
id
|
||||||
title
|
title
|
||||||
# lead
|
# lead
|
||||||
|
|
Loading…
Reference in New Issue
Block a user