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 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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ export type AuthModalSource =
|
|||
export type AuthModalSearchParams = {
|
||||
mode: AuthModalMode
|
||||
source?: AuthModalSource
|
||||
token?: string
|
||||
}
|
||||
|
||||
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) => {
|
||||
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>
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -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} />
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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> => {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue
Block a user