session-fx
Some checks failed
deploy / test (push) Failing after 1m2s
deploy / deploy (push) Has been skipped

This commit is contained in:
Untone 2023-12-24 11:16:41 +03:00
parent 62887f88c0
commit 9a55056b9d
16 changed files with 209 additions and 267 deletions

View File

@ -10,6 +10,7 @@ import { hideModal } from '../../../stores/ui'
import { PasswordField } from './PasswordField'
import styles from './AuthModal.module.scss'
import { useSession } from '../../../context/session'
type FormFields = {
password: string
@ -18,28 +19,31 @@ type FormFields = {
type ValidationErrors = Partial<Record<keyof FormFields, string | JSX.Element>>
export const ChangePasswordForm = () => {
const { changeSearchParams } = useRouter<AuthModalSearchParams>()
const { searchParams, changeSearchParams } = useRouter<AuthModalSearchParams>()
const { t } = useLocalize()
const {
actions: { changePassword },
} = useSession()
const [isSubmitting, setIsSubmitting] = createSignal(false)
const [validationErrors, setValidationErrors] = createSignal<ValidationErrors>({})
const [newPassword, setNewPassword] = createSignal<string>()
const [passwordError, setPasswordError] = createSignal<string>()
const [isSuccess, setIsSuccess] = createSignal(false)
const authFormRef: { current: HTMLFormElement } = { current: null }
const handleSubmit = async (event: Event) => {
event.preventDefault()
setIsSubmitting(true)
// Fake change password logic
console.log('!!! sent new password:', newPassword)
setTimeout(() => {
setIsSubmitting(false)
setIsSuccess(true)
}, 1000)
if (newPassword()) {
await changePassword(newPassword(), searchParams()?.token)
setTimeout(() => {
setIsSubmitting(false)
setIsSuccess(true)
}, 1000)
}
}
const handlePasswordInput = (value) => {
const handlePasswordInput = (value: string) => {
setNewPassword(value)
if (passwordError()) {
setValidationErrors((errors) => ({ ...errors, password: passwordError() }))
@ -93,6 +97,12 @@ export const ChangePasswordForm = () => {
<div class={styles.title}>{t('Password updated!')}</div>
<div class={styles.text}>{t('You can now login using your new password')}</div>
<div>
<button
class={clsx('button', styles.submitButton)}
onClick={() => changeSearchParams({ mode: 'login' })}
>
{t('Enter')}
</button>
<button class={clsx('button', styles.submitButton)} onClick={() => hideModal()}>
{t('Back to main page')}
</button>

View File

@ -1,11 +1,10 @@
import type { ConfirmEmailSearchParams } from './types'
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 { useSession } from '../../../context/session'
import { ApiError } from '../../../graphql/error'
import { useRouter } from '../../../stores/router'
import { hideModal } from '../../../stores/ui'
@ -13,52 +12,21 @@ import styles from './AuthModal.module.scss'
export const EmailConfirm = () => {
const { t } = useLocalize()
const { searchParams } = useRouter<ConfirmEmailSearchParams>()
const {
actions: { confirmEmail, loadSession, loadAuthor },
actions: { confirmEmail },
session,
} = 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)
const [isTokenInvalid, setIsTokenInvalid] = createSignal(false)
const { searchParams, changeSearchParam } = useRouter<ConfirmEmailSearchParams>()
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)
}
createEffect(async () => {
const token = searchParams()?.access_token
if (token) await confirmEmail({ token })
})
const email = createMemo(() => session()?.user?.email)
const confirmedEmail = createMemo(() => session()?.user?.email_verified)
return (
<div>

View File

@ -5,7 +5,7 @@ import { createSignal, JSX, Show } from 'solid-js'
import { useLocalize } from '../../../context/localize'
import { useSession } from '../../../context/session'
import { ApiError } from '../../../graphql/error'
// import { ApiError } from '../../../graphql/error'
import { useRouter } from '../../../stores/router'
import { validateEmail } from '../../../utils/validateEmail'
@ -33,17 +33,13 @@ export const ForgotPasswordForm = () => {
const [isSubmitting, setIsSubmitting] = createSignal(false)
const [validationErrors, setValidationErrors] = createSignal<ValidationErrors>({})
const [isUserNotFount, setIsUserNotFound] = createSignal(false)
const authFormRef: { current: HTMLFormElement } = { current: null }
const [message, setMessage] = createSignal<string>('')
const handleSubmit = async (event: Event) => {
event.preventDefault()
setSubmitError('')
setIsUserNotFound(false)
const newValidationErrors: ValidationErrors = {}
if (!email()) {
@ -53,7 +49,6 @@ export const ForgotPasswordForm = () => {
}
setValidationErrors(newValidationErrors)
const isValid = Object.keys(newValidationErrors).length === 0
if (!isValid) {
@ -68,18 +63,17 @@ export const ForgotPasswordForm = () => {
try {
const response = await authorizer().forgotPassword({
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]', response)
if (response.message) setMessage(response.message)
}
console.debug('[ForgotPasswordForm] authorizer response: ', response)
if (response && response.message) setMessage(response.message)
} catch (error) {
if (error instanceof ApiError && error.code === 'user_not_found') {
console.error(error)
if (error?.code === 'user_not_found') {
setIsUserNotFound(true)
return
}
setSubmitError(error.message)
setSubmitError(error?.message)
} finally {
setIsSubmitting(false)
}
@ -92,17 +86,17 @@ export const ForgotPasswordForm = () => {
ref={(el) => (authFormRef.current = el)}
>
<div>
<h4>{t('Forgot password?')}</h4>
<h4>{t('Restore password')}</h4>
<div class={styles.authSubtitle}>
{t(message()) || t('Everything is ok, please give us your email address')}
</div>
<div
class={clsx('pretty-form__item', {
'pretty-form__item--error': validationErrors().email,
})}
>
<input
disabled={Boolean(message())}
id="email"
name="email"
autocomplete="email"
@ -144,7 +138,11 @@ export const ForgotPasswordForm = () => {
</Show>
<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')}
</button>
</div>

View File

@ -17,6 +17,7 @@ import { email, setEmail } from './sharedLogic'
import { SocialProviders } from './SocialProviders'
import styles from './AuthModal.module.scss'
import { VerifyEmailInput } from '@authorizerdev/authorizer-js'
type FormFields = {
email: string
@ -65,11 +66,12 @@ export const LoginForm = () => {
setIsLinkSent(true)
setIsEmailNotConfirmed(false)
setSubmitError('')
const {
actions: { authorizer, getToken },
} = useSession()
const result = await authorizer().verifyEmail({ token: getToken() })
if (!result) setSubmitError('cant sign send link') // TODO:
changeSearchParams({ mode: 'forgot-password' }) // NOTE: temporary solition
/* FIXME:
const { actions: { authorizer } } = useSession()
const result = await authorizer().verifyEmail({ token })
if (!result) setSubmitError('cant sign send link')
*/
}
const handleSubmit = async (event: Event) => {
@ -110,6 +112,7 @@ export const LoginForm = () => {
showSnackbar({ body: t('Welcome!') })
} catch (error) {
console.error(error)
if (error instanceof ApiError) {
if (error.code === 'email_not_confirmed') {
setSubmitError(t('Please, confirm email'))
@ -184,16 +187,6 @@ export const LoginForm = () => {
>
{t('Forgot password?')}
</span>
<span
class="link"
onClick={() =>
changeSearchParams({
mode: 'change-password',
})
}
>
{t('Change password')}
</span>
</div>
</div>

View File

@ -6,7 +6,7 @@ import { Show, createSignal } from 'solid-js'
import { useLocalize } from '../../../context/localize'
import { useSession } from '../../../context/session'
import { ApiError } from '../../../graphql/error'
// import { ApiError } from '../../../graphql/error'
import { checkEmail, useEmailChecks } from '../../../stores/emailChecks'
import { useRouter } from '../../../stores/router'
import { hideModal } from '../../../stores/ui'
@ -36,7 +36,7 @@ export const RegisterForm = () => {
const { t } = useLocalize()
const { emailChecks } = useEmailChecks()
const {
actions: { authorizer },
actions: { signUp },
} = useSession()
const [submitError, setSubmitError] = createSignal('')
const [fullName, setFullName] = createSignal('')
@ -105,19 +105,20 @@ export const RegisterForm = () => {
}
setIsSubmitting(true)
try {
await authorizer().signup({
const opts = {
given_name: cleanName,
email: cleanEmail,
password: password(),
confirm_password: password(),
redirect_uri: window.location.origin,
})
}
await signUp(opts)
setIsSuccess(true)
} catch (error) {
if (error instanceof ApiError && error.code === 'user_already_exists') {
console.error(error)
if (error?.code === 'user_already_exists') {
return
}

View File

@ -11,6 +11,7 @@ export type AuthModalSource =
export type AuthModalSearchParams = {
mode: AuthModalMode
source?: AuthModalSource
token?: string
}
export type ConfirmEmailSearchParams = {

View File

@ -1,6 +0,0 @@
.center {
display: flex;
justify-content: center;
align-items: center;
height: 420px;
}

View File

@ -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>
</>
)
}

View File

@ -32,7 +32,7 @@ const MD_WIDTH_BREAKPOINT = 992
export const HeaderAuth = (props: Props) => {
const { t } = useLocalize()
const { page } = useRouter()
const { author, isAuthenticated, isSessionLoaded } = useSession()
const { session, author, isAuthenticated, isSessionLoaded } = useSession()
const {
unreadNotificationsCount,
actions: { showNotificationsPanel },
@ -139,12 +139,12 @@ export const HeaderAuth = (props: Props) => {
<div class={styles.button}>
<Icon
name="bell-white"
counter={isAuthenticated() ? unreadNotificationsCount() : 1}
counter={session() ? unreadNotificationsCount() || 0 : 1}
class={styles.icon}
/>
<Icon
name="bell-white-hover"
counter={isAuthenticated() ? unreadNotificationsCount() : 1}
counter={session() ? unreadNotificationsCount() || 0 : 1}
class={clsx(styles.icon, styles.iconHover)}
/>
</div>

View File

@ -67,10 +67,11 @@ export const HomeView = (props: Props) => {
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(() => {
setRandomTopic(topic)
setRandomTopicArticles(shouts)
if (result?.topic) setRandomTopic(result.topic)
if (result?.shouts) setRandomTopicArticles(result.shouts)
})
})

View File

@ -1,7 +1,7 @@
import type { JSX } from 'solid-js'
import { Link } from '@solidjs/meta'
import { splitProps } from 'solid-js'
import { createEffect, splitProps } from 'solid-js'
import { getImageUrl } from '../../../utils/getImageUrl'
@ -21,10 +21,9 @@ export const Image = (props: Props) => {
`${getImageUrl(local.src, { width: others.width * pixelDensity })} ${pixelDensity}x`,
)
.join(', ')
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} />
</>
)

View File

@ -10,10 +10,10 @@ type ShowIfAuthenticatedProps = {
}
export const ShowIfAuthenticated = (props: ShowIfAuthenticatedProps) => {
const { isAuthenticated } = useSession()
const { author } = useSession()
return (
<Show when={isAuthenticated()} fallback={props.fallback}>
<Show when={author()} fallback={props.fallback}>
{props.children}
</Show>
)

View File

@ -9,8 +9,8 @@ const RECONNECT_TIMES = 2
export interface SSEMessage {
id: string
entity: string
action: string
entity: string // follower | shout | reaction
action: string // create | delete | update | join | follow | seen
payload: any // Author | Shout | Reaction | Message
created_at?: number // unixtime x1000
seen?: boolean
@ -29,9 +29,7 @@ export const ConnectProvider = (props: { children: JSX.Element }) => {
const [messageHandlers, setHandlers] = createSignal<Array<MessageHandler>>([])
// const [messages, setMessages] = createSignal<Array<SSEMessage>>([]);
const [connected, setConnected] = createSignal(false)
const {
actions: { getToken },
} = useSession()
const { session } = useSession()
const addHandler = (handler: MessageHandler) => {
setHandlers((hhh) => [...hhh, handler])
@ -39,8 +37,9 @@ export const ConnectProvider = (props: { children: JSX.Element }) => {
const [retried, setRetried] = createSignal<number>(0)
createEffect(async () => {
const token = getToken()
const token = session()?.access_token
if (token && !connected()) {
console.info('[context.connect] init SSE connection')
await fetchEventSource('https://connect.discours.io', {
method: 'GET',
headers: {

View File

@ -8,6 +8,7 @@ import {
AuthToken,
Authorizer,
ConfigType,
SignupInput,
} from '@authorizerdev/authorizer-js'
import {
createContext,
@ -29,9 +30,9 @@ import { showModal } from '../stores/ui'
import { useLocalize } from './localize'
import { useSnackbar } from './snackbar'
const config: ConfigType = {
const defaultConfig: ConfigType = {
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?
}
@ -41,10 +42,9 @@ export type SessionContextType = {
author: Resource<Author | null>
isSessionLoaded: Accessor<boolean>
subscriptions: Accessor<Result>
isAuthenticated: Accessor<boolean>
isAuthWithCallback: Accessor<() => void>
isAuthenticated: Accessor<boolean>
actions: {
getToken: () => string
loadSession: () => AuthToken | Promise<AuthToken>
setSession: (token: AuthToken | null) => void // setSession
loadAuthor: (info?: unknown) => Author | Promise<Author>
@ -54,9 +54,11 @@ export type SessionContextType = {
callback: (() => Promise<void>) | (() => void),
modalSource: AuthModalSource,
) => void
signUp: (params: SignupInput) => Promise<AuthToken | void>
signIn: (params: LoginInput) => 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
authorizer: () => Authorizer
}
@ -81,75 +83,40 @@ export const SessionProvider = (props: {
const {
actions: { showSnackbar },
} = useSnackbar()
const { searchParams, changeSearchParam } = useRouter()
const [isSessionLoaded, setIsSessionLoaded] = createSignal(false)
const [subscriptions, setSubscriptions] = createSignal<Result>(EMPTY_SUBSCRIPTIONS)
const { searchParams, changeSearchParams } = useRouter()
const getSession = async (): Promise<AuthToken> => {
try {
const tkn = getToken()
// console.debug('[context.session] token before:', tkn)
const authResult = await authorizer().getSession({
Authorization: tkn,
// 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',
})
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)
}
})
// load
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>(
const [configuration, setConfig] = createSignal<ConfigType>(defaultConfig)
const authorizer = createMemo(() => new Authorizer(defaultConfig))
const [isSessionLoaded, setIsSessionLoaded] = createSignal(false)
const [session, { refetch: loadSession, mutate: setSession }] = createResource<AuthToken>(
async () => {
const u = session()?.user
if (u) {
return (await apiClient.getAuthorId({ user: u.id })) ?? null
try {
console.info('[context.session] loading session')
return await authorizer().getSession()
} catch (_) {
console.info('[context.session] cannot refresh session')
return null
}
return null
},
{
ssrLoadFrom: 'initial',
@ -157,29 +124,71 @@ export const SessionProvider = (props: {
},
)
const isAuthenticated = createMemo(() => Boolean(session()?.user))
const signIn = async (params: LoginInput) => {
const authResult: AuthToken | void = await authorizer().login(params)
if (authResult && authResult.access_token) {
setSession(authResult)
await loadSubscriptions()
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 [author, { refetch: loadAuthor, mutate: setAuthor }] = createResource<Author | null>(
async () => {
const u = session()?.user
return u ? (await apiClient.getAuthorId({ user: u.id })) || null : null
},
{
ssrLoadFrom: 'initial',
initialValue: null,
},
)
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(
on(
() => props.onStateChangeCallback,
@ -190,56 +199,57 @@ export const SessionProvider = (props: {
),
)
const [configuration, setConfig] = createSignal<ConfigType>(config)
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')
})
// require auth wrapper
const [isAuthWithCallback, setIsAuthWithCallback] = createSignal<() => void>()
const requireAuthentication = async (callback: () => void, modalSource: AuthModalSource) => {
setIsAuthWithCallback(() => callback)
const userdata = await authorizer().getProfile()
if (userdata) setSession({ ...session(), user: userdata })
await loadSession()
if (!isAuthenticated()) {
if (!session()) {
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 () => {
await authorizer().logout()
setSession(null)
setSubscriptions(EMPTY_SUBSCRIPTIONS)
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) => {
console.debug(`[context.session] calling authorizer's verify email with`, input)
const at: void | AuthToken = await authorizer().verifyEmail(input)
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 = {
getToken,
loadSession,
loadSubscriptions,
requireAuthentication,
signUp,
signIn,
signOut,
confirmEmail,
@ -248,16 +258,17 @@ export const SessionProvider = (props: {
setAuthor,
authorizer,
loadAuthor,
changePassword,
}
const value: SessionContextType = {
config: configuration(),
session,
subscriptions,
isSessionLoaded,
isAuthenticated,
author,
actions,
isAuthWithCallback,
isAuthenticated,
}
return <SessionContext.Provider value={value}>{props.children}</SessionContext.Provider>

View File

@ -53,39 +53,28 @@ export const apiClient = {
getRandomTopShouts: async (params: QueryLoad_Shouts_Random_TopArgs) => {
const response = await publicGraphQLClient.query(loadShoutsTopRandom, params).toPromise()
if (!response.data) {
console.error('[graphql.core] getRandomTopShouts error', response.error)
}
if (!response.data) console.error('[graphql.core] load_shouts_random_top failed', response)
return response.data.load_shouts_random_top
},
getUnratedShouts: async (limit = 50, offset = 0) => {
const response = await apiClient.private.query(loadShoutsUnrated, { limit, offset }).toPromise()
if (!response.data) {
console.error('[graphql.core] getUnratedShouts error', response.error)
}
if (!response.data) console.error('[graphql.core] load_shouts_unrated', response)
return response.data.load_shouts_unrated
},
getRandomTopics: async ({ amount }: { amount: number }) => {
const response = await publicGraphQLClient.query(topicsRandomQuery, { amount }).toPromise()
if (!response.data) {
console.error('[graphql.client.core] getRandomTopics', response.error)
}
if (!response.data) console.error('[graphql.client.core] get_topics_random failed', response)
return response.data.get_topics_random
},
getRandomTopicShouts: async (limit: number): Promise<{ topic: Topic; shouts: Shout[] }> => {
const resp = await publicGraphQLClient.query(articlesLoadRandomTopic, { limit }).toPromise()
if (resp.error) {
console.error(resp)
}
if (!resp.data) console.error('[graphql.client.core] load_shouts_random_topic', resp)
return resp.data.load_random_topics_shouts
},
@ -100,16 +89,14 @@ export const apiClient = {
getAllTopics: async () => {
const response = await publicGraphQLClient.query(topicsAll, {}).toPromise()
if (response.error) {
console.debug('[graphql.client.core] get_topics_all', response.error)
}
if (!response.data) console.error('[graphql.client.core] get_topics_all', response)
return response.data.get_topics_all
},
getAllAuthors: async (limit: number = 50, offset: number = 0) => {
const response = await publicGraphQLClient.query(authorsAll, { limit, offset }).toPromise()
if (response.error) {
console.debug('[graphql.client.core] load_authors_all', response.error)
}
if (!response.data) console.error('[graphql.client.core] load_authors_all', response)
return response.data.load_authors_all
},
getAuthor: async (params: { slug?: string; author_id?: number }): Promise<Author> => {

View File

@ -1,8 +1,8 @@
import { gql } from '@urql/core'
export default gql`
query LoadRandomTopShoutsQuery($params: LoadRandomTopShoutsParams) {
load_shouts_random_top(params: $params) {
query LoadRandomTopShoutsQuery($options: LoadShoutsOptions) {
load_shouts_random_top(options: $options) {
id
title
# lead