rooting authorizer
This commit is contained in:
parent
dfbbb075e8
commit
a59cc9c28e
|
@ -4,6 +4,7 @@ import { Meta, MetaProvider } from '@solidjs/meta'
|
||||||
import { Component, createEffect, createMemo } from 'solid-js'
|
import { Component, createEffect, createMemo } from 'solid-js'
|
||||||
import { Dynamic } from 'solid-js/web'
|
import { Dynamic } from 'solid-js/web'
|
||||||
|
|
||||||
|
import { AuthorizerProvider } from '../context/authorizer'
|
||||||
import { ConfirmProvider } from '../context/confirm'
|
import { ConfirmProvider } from '../context/confirm'
|
||||||
import { ConnectProvider } from '../context/connect'
|
import { ConnectProvider } from '../context/connect'
|
||||||
import { EditorProvider } from '../context/editor'
|
import { EditorProvider } from '../context/editor'
|
||||||
|
@ -41,7 +42,6 @@ import { SearchPage } from '../pages/search.page'
|
||||||
import { TopicPage } from '../pages/topic.page'
|
import { TopicPage } from '../pages/topic.page'
|
||||||
import { ROUTES, useRouter } from '../stores/router'
|
import { ROUTES, useRouter } from '../stores/router'
|
||||||
import { hideModal, MODALS, showModal } from '../stores/ui'
|
import { hideModal, MODALS, showModal } from '../stores/ui'
|
||||||
import { AuthorizerProvider } from '../context/authorizer'
|
|
||||||
|
|
||||||
// TODO: lazy load
|
// TODO: lazy load
|
||||||
// const SomePage = lazy(() => import('./Pages/SomePage'))
|
// const SomePage = lazy(() => import('./Pages/SomePage'))
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { Author, Shout } from '../../graphql/schema/core.gen'
|
import type { Author, Shout, Topic } from '../../graphql/schema/core.gen'
|
||||||
|
|
||||||
import { getPagePath } from '@nanostores/router'
|
import { getPagePath } from '@nanostores/router'
|
||||||
import { createPopper } from '@popperjs/core'
|
import { createPopper } from '@popperjs/core'
|
||||||
|
@ -12,6 +12,7 @@ import { useReactions } from '../../context/reactions'
|
||||||
import { useSession } from '../../context/session'
|
import { useSession } from '../../context/session'
|
||||||
import { MediaItem } from '../../pages/types'
|
import { MediaItem } from '../../pages/types'
|
||||||
import { DEFAULT_HEADER_OFFSET, router, useRouter } from '../../stores/router'
|
import { DEFAULT_HEADER_OFFSET, router, useRouter } from '../../stores/router'
|
||||||
|
import { capitalize } from '../../utils/capitalize'
|
||||||
import { getImageUrl } from '../../utils/getImageUrl'
|
import { getImageUrl } from '../../utils/getImageUrl'
|
||||||
import { getDescription } from '../../utils/meta'
|
import { getDescription } from '../../utils/meta'
|
||||||
import { Icon } from '../_shared/Icon'
|
import { Icon } from '../_shared/Icon'
|
||||||
|
@ -33,7 +34,6 @@ import { ShoutRatingControl } from './ShoutRatingControl'
|
||||||
|
|
||||||
import styles from './Article.module.scss'
|
import styles from './Article.module.scss'
|
||||||
import stylesHeader from '../Nav/Header/Header.module.scss'
|
import stylesHeader from '../Nav/Header/Header.module.scss'
|
||||||
import { capitalize } from '../../utils/capitalize'
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
article: Shout
|
article: Shout
|
||||||
|
@ -73,7 +73,7 @@ export const FullArticle = (props: Props) => {
|
||||||
|
|
||||||
const mainTopic = createMemo(() => {
|
const mainTopic = createMemo(() => {
|
||||||
const main_topic_slug = props.article.topics.length > 0 ? props.article.main_topic : null
|
const main_topic_slug = props.article.topics.length > 0 ? props.article.main_topic : null
|
||||||
const mt = props.article.topics.find((t) => t.slug === main_topic_slug)
|
const mt = props.article.topics.find((tpc: Topic) => tpc.slug === main_topic_slug)
|
||||||
if (mt) {
|
if (mt) {
|
||||||
mt.title = lang() == 'en' ? capitalize(mt.slug.replace('-', ' ')) : mt.title
|
mt.title = lang() == 'en' ? capitalize(mt.slug.replace('-', ' ')) : mt.title
|
||||||
return mt
|
return mt
|
||||||
|
|
|
@ -32,7 +32,7 @@ export const AuthorBadge = (props: Props) => {
|
||||||
const { changeSearchParam } = useRouter()
|
const { changeSearchParam } = useRouter()
|
||||||
const { t, formatDate } = useLocalize()
|
const { t, formatDate } = useLocalize()
|
||||||
const subscribed = createMemo(() =>
|
const subscribed = createMemo(() =>
|
||||||
subscriptions().authors.some((author) => author.slug === props.author.slug),
|
subscriptions().authors.some((a: Author) => a.slug === props.author.slug),
|
||||||
)
|
)
|
||||||
|
|
||||||
const subscribe = async (really = true) => {
|
const subscribe = async (really = true) => {
|
||||||
|
|
|
@ -43,7 +43,7 @@ export const AuthorCard = (props: Props) => {
|
||||||
const [subscriptionFilter, setSubscriptionFilter] = createSignal<SubscriptionFilter>('all')
|
const [subscriptionFilter, setSubscriptionFilter] = createSignal<SubscriptionFilter>('all')
|
||||||
|
|
||||||
const subscribed = createMemo<boolean>(() =>
|
const subscribed = createMemo<boolean>(() =>
|
||||||
subscriptions().authors.some((author) => author.slug === props.author.slug),
|
subscriptions().authors.some((a: Author) => a.slug === props.author.slug),
|
||||||
)
|
)
|
||||||
|
|
||||||
const subscribe = async (really = true) => {
|
const subscribe = async (really = true) => {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { Author, Shout } from '../../../graphql/schema/core.gen'
|
import type { Author, Shout, Topic } from '../../../graphql/schema/core.gen'
|
||||||
|
|
||||||
import { getPagePath, openPage } from '@nanostores/router'
|
import { getPagePath, openPage } from '@nanostores/router'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
|
@ -87,7 +87,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
|
||||||
const { t, lang, formatDate } = useLocalize()
|
const { t, lang, formatDate } = useLocalize()
|
||||||
const { author } = useSession()
|
const { author } = useSession()
|
||||||
const mainTopicSlug = props.article.main_topic
|
const mainTopicSlug = props.article.main_topic
|
||||||
const mainTopic = props.article.topics.find((t) => t.slug === mainTopicSlug)
|
const mainTopic = props.article.topics.find((tpc: Topic) => tpc.slug === mainTopicSlug)
|
||||||
const mainTopicTitle =
|
const mainTopicTitle =
|
||||||
lang() === 'ru' && mainTopic?.title ? mainTopic.title : mainTopicSlug.replace('-', ' ')
|
lang() === 'ru' && mainTopic?.title ? mainTopic.title : mainTopicSlug.replace('-', ' ')
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import type { ConfirmEmailSearchParams } from './types'
|
||||||
import { clsx } from 'clsx'
|
import { clsx } from 'clsx'
|
||||||
import { createMemo, createSignal, onMount, Show } from 'solid-js'
|
import { createMemo, createSignal, onMount, Show } from 'solid-js'
|
||||||
|
|
||||||
|
import { useAuthorizer } from '../../../context/authorizer'
|
||||||
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'
|
||||||
|
@ -14,14 +15,14 @@ import styles from './AuthModal.module.scss'
|
||||||
export const EmailConfirm = () => {
|
export const EmailConfirm = () => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
const {
|
const {
|
||||||
session,
|
|
||||||
actions: { confirmEmail },
|
actions: { confirmEmail },
|
||||||
} = useSession()
|
} = useSession()
|
||||||
|
const [{ user }] = useAuthorizer()
|
||||||
|
|
||||||
const [isTokenExpired, setIsTokenExpired] = createSignal(false)
|
const [isTokenExpired, setIsTokenExpired] = createSignal(false)
|
||||||
const [isTokenInvalid, setIsTokenInvalid] = createSignal(false)
|
const [isTokenInvalid, setIsTokenInvalid] = createSignal(false)
|
||||||
|
|
||||||
const confirmedEmail = createMemo(() => session()?.user?.email || '')
|
const confirmedEmail = createMemo(() => user?.email || '')
|
||||||
|
|
||||||
const { searchParams } = useRouter<ConfirmEmailSearchParams>()
|
const { searchParams } = useRouter<ConfirmEmailSearchParams>()
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,10 @@ import deepEqual from 'fast-deep-equal'
|
||||||
import { createEffect, createSignal, For, lazy, Match, onCleanup, onMount, Show, Switch } from 'solid-js'
|
import { createEffect, createSignal, For, lazy, Match, onCleanup, onMount, Show, Switch } from 'solid-js'
|
||||||
import { createStore } from 'solid-js/store'
|
import { createStore } from 'solid-js/store'
|
||||||
|
|
||||||
|
import { useAuthorizer } from '../../context/authorizer'
|
||||||
import { useConfirm } from '../../context/confirm'
|
import { useConfirm } from '../../context/confirm'
|
||||||
import { useLocalize } from '../../context/localize'
|
import { useLocalize } from '../../context/localize'
|
||||||
import { useProfileForm } from '../../context/profile'
|
import { useProfileForm } from '../../context/profile'
|
||||||
import { useSession } from '../../context/session'
|
|
||||||
import { useSnackbar } from '../../context/snackbar'
|
import { useSnackbar } from '../../context/snackbar'
|
||||||
import { clone } from '../../utils/clone'
|
import { clone } from '../../utils/clone'
|
||||||
import { getImageUrl } from '../../utils/getImageUrl'
|
import { getImageUrl } from '../../utils/getImageUrl'
|
||||||
|
@ -49,9 +49,7 @@ export const ProfileSettings = () => {
|
||||||
actions: { showSnackbar },
|
actions: { showSnackbar },
|
||||||
} = useSnackbar()
|
} = useSnackbar()
|
||||||
|
|
||||||
const {
|
const [, { setUser, authorizer }] = useAuthorizer()
|
||||||
actions: { loadSession },
|
|
||||||
} = useSession()
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
actions: { showConfirm },
|
actions: { showConfirm },
|
||||||
|
@ -101,7 +99,10 @@ export const ProfileSettings = () => {
|
||||||
}
|
}
|
||||||
showSnackbar({ type: 'error', body: t('Error') })
|
showSnackbar({ type: 'error', body: t('Error') })
|
||||||
}
|
}
|
||||||
loadSession()
|
const profile = await authorizer().getProfile()
|
||||||
|
if (profile) {
|
||||||
|
setUser(profile)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCancel = async () => {
|
const handleCancel = async () => {
|
||||||
|
|
|
@ -84,7 +84,7 @@ export const TopicCard = (props: TopicProps) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const title = createMemo(() =>
|
const title = createMemo(() =>
|
||||||
capitalize(lang() == 'en' ? props.topic.slug.replace(/-/g, ' ') : props.topic.title || ''),
|
capitalize(lang() == 'en' ? props.topic.slug.replaceAll('-', ' ') : props.topic.title || ''),
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -5,12 +5,12 @@ import { useLocalize } from '../../../context/localize'
|
||||||
import { useSession } from '../../../context/session'
|
import { useSession } from '../../../context/session'
|
||||||
import { FollowingEntity, Topic } from '../../../graphql/schema/core.gen'
|
import { FollowingEntity, Topic } from '../../../graphql/schema/core.gen'
|
||||||
import { follow, unfollow } from '../../../stores/zine/common'
|
import { follow, unfollow } from '../../../stores/zine/common'
|
||||||
|
import { capitalize } from '../../../utils/capitalize'
|
||||||
import { getImageUrl } from '../../../utils/getImageUrl'
|
import { getImageUrl } from '../../../utils/getImageUrl'
|
||||||
import { Button } from '../../_shared/Button'
|
import { Button } from '../../_shared/Button'
|
||||||
import { CheckButton } from '../../_shared/CheckButton'
|
import { CheckButton } from '../../_shared/CheckButton'
|
||||||
|
|
||||||
import styles from './TopicBadge.module.scss'
|
import styles from './TopicBadge.module.scss'
|
||||||
import { capitalize } from '../../../utils/capitalize'
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
topic: Topic
|
topic: Topic
|
||||||
|
@ -54,7 +54,7 @@ export const TopicBadge = (props: Props) => {
|
||||||
/>
|
/>
|
||||||
<a href={`/topic/${props.topic.slug}`} class={styles.info}>
|
<a href={`/topic/${props.topic.slug}`} class={styles.info}>
|
||||||
<span class={styles.title}>
|
<span class={styles.title}>
|
||||||
{lang() == 'en' ? capitalize(props.topic.slug.replace(/-/g, ' ')) : props.topic.title}
|
{lang() == 'en' ? capitalize(props.topic.slug.replaceAll('-', ' ')) : props.topic.title}
|
||||||
</span>
|
</span>
|
||||||
<Show
|
<Show
|
||||||
when={props.topic.body}
|
when={props.topic.body}
|
||||||
|
|
|
@ -7,13 +7,13 @@ import { useLocalize } from '../../context/localize'
|
||||||
import { useSession } from '../../context/session'
|
import { useSession } from '../../context/session'
|
||||||
import { useRouter } from '../../stores/router'
|
import { useRouter } from '../../stores/router'
|
||||||
import { setTopicsSort, useTopicsStore } from '../../stores/zine/topics'
|
import { setTopicsSort, useTopicsStore } from '../../stores/zine/topics'
|
||||||
|
import { capitalize } from '../../utils/capitalize'
|
||||||
import { dummyFilter } from '../../utils/dummyFilter'
|
import { dummyFilter } from '../../utils/dummyFilter'
|
||||||
import { scrollHandler } from '../../utils/scroll'
|
import { scrollHandler } from '../../utils/scroll'
|
||||||
import { SearchField } from '../_shared/SearchField'
|
import { SearchField } from '../_shared/SearchField'
|
||||||
import { TopicCard } from '../Topic/Card'
|
import { TopicCard } from '../Topic/Card'
|
||||||
|
|
||||||
import styles from './AllTopics.module.scss'
|
import styles from './AllTopics.module.scss'
|
||||||
import { capitalize } from '../../utils/capitalize'
|
|
||||||
|
|
||||||
type AllTopicsPageSearchParams = {
|
type AllTopicsPageSearchParams = {
|
||||||
by: 'shouts' | 'authors' | 'title' | ''
|
by: 'shouts' | 'authors' | 'title' | ''
|
||||||
|
@ -147,7 +147,9 @@ export const AllTopicsView = (props: AllTopicsViewProps) => {
|
||||||
{(topic) => (
|
{(topic) => (
|
||||||
<div class={clsx(styles.topic, 'topic col-sm-12 col-md-8')}>
|
<div class={clsx(styles.topic, 'topic col-sm-12 col-md-8')}>
|
||||||
<a href={`/topic/${topic.slug}`}>
|
<a href={`/topic/${topic.slug}`}>
|
||||||
{lang() == 'en' ? capitalize(topic.slug.replace(/-/g, ' ')) : topic.title}
|
{lang() == 'en'
|
||||||
|
? capitalize(topic.slug.replaceAll('-', ' '))
|
||||||
|
: topic.title}
|
||||||
</a>
|
</a>
|
||||||
<span class={styles.articlesCounter}>{topic.stat.shouts}</span>
|
<span class={styles.articlesCounter}>{topic.stat.shouts}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -409,7 +409,7 @@ export const EditView = (props: Props) => {
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<Show when={page().route === 'editSettings'}>
|
<Show when={page().route === 'editSettings'}>
|
||||||
<PublishSettings shoutId={props.shout.id} form={form}></PublishSettings>
|
<PublishSettings shoutId={props.shout.id} form={form} />
|
||||||
</Show>
|
</Show>
|
||||||
<Panel shoutId={props.shout.id} />
|
<Panel shoutId={props.shout.id} />
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -228,7 +228,7 @@ export const FeedView = (props: Props) => {
|
||||||
{(topic) => (
|
{(topic) => (
|
||||||
<span class={clsx(stylesTopic.shoutTopic, styles.topic)}>
|
<span class={clsx(stylesTopic.shoutTopic, styles.topic)}>
|
||||||
<a href={`/topic/${topic.slug}`}>
|
<a href={`/topic/${topic.slug}`}>
|
||||||
{lang() == 'en' ? topic.slug.replace(/-/g, ' ') : topic.title}
|
{lang() == 'en' ? topic.slug.replaceAll('-', ' ') : topic.title}
|
||||||
</a>{' '}
|
</a>{' '}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -20,7 +20,7 @@ import stylesSettings from '../../../styles/FeedSettings.module.scss'
|
||||||
|
|
||||||
export const ProfileSubscriptions = () => {
|
export const ProfileSubscriptions = () => {
|
||||||
const { t, lang } = useLocalize()
|
const { t, lang } = useLocalize()
|
||||||
const { session } = useSession()
|
const { author } = useSession()
|
||||||
const [following, setFollowing] = createSignal<Array<Author | Topic>>([])
|
const [following, setFollowing] = createSignal<Array<Author | Topic>>([])
|
||||||
const [filtered, setFiltered] = createSignal<Array<Author | Topic>>([])
|
const [filtered, setFiltered] = createSignal<Array<Author | Topic>>([])
|
||||||
const [subscriptionFilter, setSubscriptionFilter] = createSignal<SubscriptionFilter>('all')
|
const [subscriptionFilter, setSubscriptionFilter] = createSignal<SubscriptionFilter>('all')
|
||||||
|
@ -29,8 +29,8 @@ export const ProfileSubscriptions = () => {
|
||||||
const fetchSubscriptions = async () => {
|
const fetchSubscriptions = async () => {
|
||||||
try {
|
try {
|
||||||
const [getAuthors, getTopics] = await Promise.all([
|
const [getAuthors, getTopics] = await Promise.all([
|
||||||
apiClient.getAuthorFollowingUsers({ slug: session()?.author.slug }),
|
apiClient.getAuthorFollowingUsers({ slug: author()?.slug }),
|
||||||
apiClient.getAuthorFollowingTopics({ slug: session()?.author.slug }),
|
apiClient.getAuthorFollowingTopics({ slug: author()?.slug }),
|
||||||
])
|
])
|
||||||
setFollowing([...getAuthors, ...getTopics])
|
setFollowing([...getAuthors, ...getTopics])
|
||||||
setFiltered([...getAuthors, ...getTopics])
|
setFiltered([...getAuthors, ...getTopics])
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { Topic } from '../../../graphql/schema/core.gen'
|
||||||
import { UploadedFile } from '../../../pages/types'
|
import { UploadedFile } from '../../../pages/types'
|
||||||
import { router } from '../../../stores/router'
|
import { router } from '../../../stores/router'
|
||||||
import { hideModal, showModal } from '../../../stores/ui'
|
import { hideModal, showModal } from '../../../stores/ui'
|
||||||
|
import { useTopicsStore } from '../../../stores/zine/topics'
|
||||||
import { Button } from '../../_shared/Button'
|
import { Button } from '../../_shared/Button'
|
||||||
import { Icon } from '../../_shared/Icon'
|
import { Icon } from '../../_shared/Icon'
|
||||||
import { Image } from '../../_shared/Image'
|
import { Image } from '../../_shared/Image'
|
||||||
|
@ -19,7 +20,6 @@ import { EMPTY_TOPIC } from '../Edit'
|
||||||
|
|
||||||
import styles from './PublishSettings.module.scss'
|
import styles from './PublishSettings.module.scss'
|
||||||
import stylesBeside from '../../Feed/Beside.module.scss'
|
import stylesBeside from '../../Feed/Beside.module.scss'
|
||||||
import { useTopicsStore } from '../../../stores/zine/topics'
|
|
||||||
|
|
||||||
const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor'))
|
const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor'))
|
||||||
const GrowingTextarea = lazy(() => import('../../_shared/GrowingTextarea/GrowingTextarea'))
|
const GrowingTextarea = lazy(() => import('../../_shared/GrowingTextarea/GrowingTextarea'))
|
||||||
|
|
|
@ -1,15 +1,7 @@
|
||||||
import type { ParentComponent } from 'solid-js'
|
import type { ParentComponent } from 'solid-js'
|
||||||
|
|
||||||
import { Authorizer, User, AuthToken, ConfigType } from '@authorizerdev/authorizer-js'
|
import { Authorizer, User, AuthToken, ConfigType } from '@authorizerdev/authorizer-js'
|
||||||
import {
|
import { createContext, createEffect, createMemo, onMount, useContext } from 'solid-js'
|
||||||
createContext,
|
|
||||||
createEffect,
|
|
||||||
createMemo,
|
|
||||||
createSignal,
|
|
||||||
onCleanup,
|
|
||||||
onMount,
|
|
||||||
useContext,
|
|
||||||
} from 'solid-js'
|
|
||||||
import { createStore } from 'solid-js/store'
|
import { createStore } from 'solid-js/store'
|
||||||
|
|
||||||
export type AuthorizerState = {
|
export type AuthorizerState = {
|
||||||
|
@ -83,18 +75,6 @@ export const AuthorizerProvider: ParentComponent<AuthorizerProviderProps> = (pro
|
||||||
|
|
||||||
const handleTokenChange = (token: AuthToken | null) => {
|
const handleTokenChange = (token: AuthToken | null) => {
|
||||||
setState('token', token)
|
setState('token', token)
|
||||||
|
|
||||||
// If we have an access_token, then we clear the interval and create a new interval
|
|
||||||
// to the token expires_in, so we can retrieve the token again before it expires
|
|
||||||
if (token?.access_token) {
|
|
||||||
if (interval) {
|
|
||||||
clearInterval(interval)
|
|
||||||
}
|
|
||||||
|
|
||||||
interval = setInterval(() => {
|
|
||||||
getToken()
|
|
||||||
}, token.expires_in * 1000) as any
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const setUser = (user: User | null) => {
|
const setUser = (user: User | null) => {
|
||||||
|
@ -110,53 +90,17 @@ export const AuthorizerProvider: ParentComponent<AuthorizerProviderProps> = (pro
|
||||||
setState('user', null)
|
setState('user', null)
|
||||||
}
|
}
|
||||||
|
|
||||||
let interval: number | null = null
|
const interval: number | null = null
|
||||||
|
|
||||||
const getToken = async () => {
|
const getToken = async () => {
|
||||||
setState('loading', true)
|
setState('loading', true)
|
||||||
const metaRes = await authorizer().getMetaData()
|
const metaRes = await authorizer().getMetaData()
|
||||||
|
|
||||||
try {
|
|
||||||
const res = await authorizer().getSession()
|
|
||||||
if (res.access_token && res.user) {
|
|
||||||
setState((prev) => ({
|
|
||||||
...prev,
|
|
||||||
token: {
|
|
||||||
access_token: res.access_token,
|
|
||||||
expires_in: res.expires_in,
|
|
||||||
id_token: res.id_token,
|
|
||||||
refresh_token: res.refresh_token || '',
|
|
||||||
},
|
|
||||||
user: res.user,
|
|
||||||
}))
|
|
||||||
|
|
||||||
if (interval) {
|
|
||||||
clearInterval(interval)
|
|
||||||
}
|
|
||||||
|
|
||||||
interval = setInterval(() => {
|
|
||||||
getToken()
|
|
||||||
}, res.expires_in * 1000) as any
|
|
||||||
} else {
|
|
||||||
setState((prev) => ({ ...prev, user: null, token: null }))
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
setState((prev) => ({ ...prev, user: null, token: null }))
|
|
||||||
} finally {
|
|
||||||
setState('config', (cfg) => ({ ...cfg, ...metaRes }))
|
setState('config', (cfg) => ({ ...cfg, ...metaRes }))
|
||||||
setState('loading', false)
|
setState('loading', false)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
setState('config', { ...config, redirectURL: window.location.origin + '/?modal=auth' })
|
setState('config', { ...config, redirectURL: window.location.origin + '/?modal=auth' })
|
||||||
!state.token && getToken()
|
|
||||||
})
|
|
||||||
|
|
||||||
onCleanup(() => {
|
|
||||||
if (interval) {
|
|
||||||
clearInterval(interval)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -3,8 +3,8 @@ import type { Accessor, JSX } from 'solid-js'
|
||||||
import { fetchEventSource } from '@microsoft/fetch-event-source'
|
import { fetchEventSource } from '@microsoft/fetch-event-source'
|
||||||
import { createContext, useContext, createSignal, createEffect } from 'solid-js'
|
import { createContext, useContext, createSignal, createEffect } from 'solid-js'
|
||||||
|
|
||||||
import { useSession } from './session'
|
|
||||||
import { useAuthorizer } from './authorizer'
|
import { useAuthorizer } from './authorizer'
|
||||||
|
import { useSession } from './session'
|
||||||
|
|
||||||
export interface SSEMessage {
|
export interface SSEMessage {
|
||||||
id: string
|
id: string
|
||||||
|
|
|
@ -12,6 +12,7 @@ import { slugify } from '../utils/slugify'
|
||||||
|
|
||||||
import { useLocalize } from './localize'
|
import { useLocalize } from './localize'
|
||||||
import { useSnackbar } from './snackbar'
|
import { useSnackbar } from './snackbar'
|
||||||
|
import { useSession } from './session'
|
||||||
|
|
||||||
type WordCounter = {
|
type WordCounter = {
|
||||||
characters: number
|
characters: number
|
||||||
|
@ -82,10 +83,13 @@ const removeDraftFromLocalStorage = (shoutId: number) => {
|
||||||
|
|
||||||
export const EditorProvider = (props: { children: JSX.Element }) => {
|
export const EditorProvider = (props: { children: JSX.Element }) => {
|
||||||
const { t } = useLocalize()
|
const { t } = useLocalize()
|
||||||
|
const {
|
||||||
|
actions: { getToken },
|
||||||
|
} = useSession()
|
||||||
const { page } = useRouter()
|
const { page } = useRouter()
|
||||||
const apiClient = createMemo(() => {
|
const apiClient = createMemo(() => {
|
||||||
if (!coreClient.private) coreClient.connect()
|
const token = getToken()
|
||||||
|
if (!coreClient.private) coreClient.connect(token)
|
||||||
return coreClient
|
return coreClient
|
||||||
})
|
})
|
||||||
const {
|
const {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { inboxClient } from '../graphql/client/chat'
|
||||||
import { loadMessages } from '../stores/inbox'
|
import { loadMessages } from '../stores/inbox'
|
||||||
|
|
||||||
import { SSEMessage, useConnect } from './connect'
|
import { SSEMessage, useConnect } from './connect'
|
||||||
|
import { useSession } from './session'
|
||||||
|
|
||||||
type InboxContextType = {
|
type InboxContextType = {
|
||||||
chats: Accessor<Chat[]>
|
chats: Accessor<Chat[]>
|
||||||
|
@ -40,8 +41,12 @@ export const InboxProvider = (props: { children: JSX.Element }) => {
|
||||||
setChats((prev) => [...prev, relivedChat])
|
setChats((prev) => [...prev, relivedChat])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const {
|
||||||
|
actions: { getToken },
|
||||||
|
} = useSession()
|
||||||
const apiClient = createMemo(() => {
|
const apiClient = createMemo(() => {
|
||||||
if (!inboxClient.private) inboxClient.connect()
|
const token = getToken()
|
||||||
|
if (!inboxClient.private) inboxClient.connect(token)
|
||||||
return inboxClient
|
return inboxClient
|
||||||
})
|
})
|
||||||
const { addHandler } = useConnect()
|
const { addHandler } = useConnect()
|
||||||
|
|
|
@ -39,9 +39,13 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => {
|
||||||
const [unreadNotificationsCount, setUnreadNotificationsCount] = createSignal(0)
|
const [unreadNotificationsCount, setUnreadNotificationsCount] = createSignal(0)
|
||||||
const [totalNotificationsCount, setTotalNotificationsCount] = createSignal(0)
|
const [totalNotificationsCount, setTotalNotificationsCount] = createSignal(0)
|
||||||
const [notificationEntities, setNotificationEntities] = createStore<Record<number, Notification>>({})
|
const [notificationEntities, setNotificationEntities] = createStore<Record<number, Notification>>({})
|
||||||
const { isAuthenticated } = useSession()
|
const {
|
||||||
|
isAuthenticated,
|
||||||
|
actions: { getToken },
|
||||||
|
} = useSession()
|
||||||
const apiClient = createMemo(() => {
|
const apiClient = createMemo(() => {
|
||||||
if (!notifierClient.private && isAuthenticated()) notifierClient.connect()
|
const token = getToken()
|
||||||
|
if (!notifierClient.private && isAuthenticated()) notifierClient.connect(token)
|
||||||
return notifierClient
|
return notifierClient
|
||||||
})
|
})
|
||||||
const { addHandler } = useConnect()
|
const { addHandler } = useConnect()
|
||||||
|
|
|
@ -30,13 +30,17 @@ const userpicUrl = (userpic: string) => {
|
||||||
return userpic
|
return userpic
|
||||||
}
|
}
|
||||||
export const ProfileFormProvider = (props: { children: JSX.Element }) => {
|
export const ProfileFormProvider = (props: { children: JSX.Element }) => {
|
||||||
const { author: currentAuthor } = useSession()
|
const {
|
||||||
|
author,
|
||||||
|
actions: { getToken },
|
||||||
|
} = useSession()
|
||||||
const [form, setForm] = createStore<ProfileInput>({})
|
const [form, setForm] = createStore<ProfileInput>({})
|
||||||
|
|
||||||
const currentSlug = createMemo(() => session()?.user?.slug)
|
const currentSlug = createMemo(() => author()?.slug)
|
||||||
|
|
||||||
const apiClient = createMemo(() => {
|
const apiClient = createMemo(() => {
|
||||||
if (!coreClient.private) coreClient.connect()
|
const token = getToken()
|
||||||
|
if (!coreClient.private) coreClient.connect(token)
|
||||||
return coreClient
|
return coreClient
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,8 @@ import { createStore, reconcile } from 'solid-js/store'
|
||||||
import { apiClient as coreClient } from '../graphql/client/core'
|
import { apiClient as coreClient } from '../graphql/client/core'
|
||||||
import { Reaction, ReactionBy, ReactionInput, ReactionKind } from '../graphql/schema/core.gen'
|
import { Reaction, ReactionBy, ReactionInput, ReactionKind } from '../graphql/schema/core.gen'
|
||||||
|
|
||||||
|
import { useSession } from './session'
|
||||||
|
|
||||||
type ReactionsContextType = {
|
type ReactionsContextType = {
|
||||||
reactionEntities: Record<number, Reaction>
|
reactionEntities: Record<number, Reaction>
|
||||||
actions: {
|
actions: {
|
||||||
|
@ -32,9 +34,13 @@ export function useReactions() {
|
||||||
|
|
||||||
export const ReactionsProvider = (props: { children: JSX.Element }) => {
|
export const ReactionsProvider = (props: { children: JSX.Element }) => {
|
||||||
const [reactionEntities, setReactionEntities] = createStore<Record<number, Reaction>>({})
|
const [reactionEntities, setReactionEntities] = createStore<Record<number, Reaction>>({})
|
||||||
|
const {
|
||||||
|
actions: { getToken },
|
||||||
|
} = useSession()
|
||||||
|
|
||||||
const apiClient = createMemo(() => {
|
const apiClient = createMemo(() => {
|
||||||
if (!coreClient.private) coreClient.connect()
|
const token = getToken()
|
||||||
|
if (!coreClient.private) coreClient.connect(token)
|
||||||
return coreClient
|
return coreClient
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ import type { Author, Result } from '../graphql/schema/core.gen'
|
||||||
import type { Accessor, JSX, Resource } from 'solid-js'
|
import type { Accessor, JSX, Resource } from 'solid-js'
|
||||||
|
|
||||||
import { VerifyEmailInput, LoginInput, AuthToken, User } from '@authorizerdev/authorizer-js'
|
import { VerifyEmailInput, LoginInput, AuthToken, User } from '@authorizerdev/authorizer-js'
|
||||||
import { cookieStorage, createStorage } from '@solid-primitives/storage'
|
|
||||||
import { createContext, createMemo, createResource, createSignal, onMount, useContext } from 'solid-js'
|
import { createContext, createMemo, createResource, createSignal, onMount, useContext } from 'solid-js'
|
||||||
|
|
||||||
import { apiClient } from '../graphql/client/core'
|
import { apiClient } from '../graphql/client/core'
|
||||||
|
@ -12,13 +11,11 @@ import { showModal } from '../stores/ui'
|
||||||
import { useAuthorizer } from './authorizer'
|
import { useAuthorizer } from './authorizer'
|
||||||
import { useLocalize } from './localize'
|
import { useLocalize } from './localize'
|
||||||
import { useSnackbar } from './snackbar'
|
import { useSnackbar } from './snackbar'
|
||||||
import { getToken, resetToken, setToken } from '../stores/token'
|
|
||||||
|
|
||||||
export type SessionContextType = {
|
export type SessionContextType = {
|
||||||
session: Resource<AuthToken>
|
session: Resource<AuthToken>
|
||||||
isSessionLoaded: Accessor<boolean>
|
isSessionLoaded: Accessor<boolean>
|
||||||
subscriptions: Accessor<Result>
|
subscriptions: Accessor<Result>
|
||||||
user: Accessor<User>
|
|
||||||
author: Resource<Author | null>
|
author: Resource<Author | null>
|
||||||
isAuthenticated: Accessor<boolean>
|
isAuthenticated: Accessor<boolean>
|
||||||
actions: {
|
actions: {
|
||||||
|
@ -53,8 +50,8 @@ export const SessionProvider = (props: { children: JSX.Element }) => {
|
||||||
const {
|
const {
|
||||||
actions: { showSnackbar },
|
actions: { showSnackbar },
|
||||||
} = useSnackbar()
|
} = useSnackbar()
|
||||||
const [, { authorizer }] = useAuthorizer()
|
const [{ token }, { setUser, setToken, authorizer }] = useAuthorizer()
|
||||||
|
const getToken = () => token.access_token
|
||||||
const loadSubscriptions = async (): Promise<void> => {
|
const loadSubscriptions = async (): Promise<void> => {
|
||||||
const result = await apiClient.getMySubscriptions()
|
const result = await apiClient.getMySubscriptions()
|
||||||
if (result) {
|
if (result) {
|
||||||
|
@ -66,14 +63,12 @@ export const SessionProvider = (props: { children: JSX.Element }) => {
|
||||||
|
|
||||||
const getSession = async (): Promise<AuthToken> => {
|
const getSession = async (): Promise<AuthToken> => {
|
||||||
try {
|
try {
|
||||||
const token = getToken()
|
|
||||||
if (token) {
|
if (token) {
|
||||||
const authResult = await authorizer().getSession({
|
const authResult = await authorizer().getSession()
|
||||||
Authorization: token,
|
|
||||||
})
|
|
||||||
if (authResult && authResult.access_token) {
|
if (authResult && authResult.access_token) {
|
||||||
console.log(authResult)
|
console.log(authResult)
|
||||||
setToken(authResult.access_token)
|
setToken(authResult)
|
||||||
|
if (authResult.user) setUser(authResult.user)
|
||||||
loadSubscriptions()
|
loadSubscriptions()
|
||||||
return authResult
|
return authResult
|
||||||
}
|
}
|
||||||
|
@ -81,7 +76,8 @@ export const SessionProvider = (props: { children: JSX.Element }) => {
|
||||||
return null
|
return null
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('getSession error:', error)
|
console.error('getSession error:', error)
|
||||||
resetToken()
|
setToken(null)
|
||||||
|
setUser(null)
|
||||||
return null
|
return null
|
||||||
} finally {
|
} finally {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
@ -95,8 +91,6 @@ export const SessionProvider = (props: { children: JSX.Element }) => {
|
||||||
initialValue: null,
|
initialValue: null,
|
||||||
})
|
})
|
||||||
|
|
||||||
const user = createMemo(() => session()?.user)
|
|
||||||
|
|
||||||
const [author, { refetch: loadAuthor }] = createResource<Author | null>(
|
const [author, { refetch: loadAuthor }] = createResource<Author | null>(
|
||||||
async () => {
|
async () => {
|
||||||
const u = session()?.user
|
const u = session()?.user
|
||||||
|
@ -117,7 +111,7 @@ export const SessionProvider = (props: { children: JSX.Element }) => {
|
||||||
const authResult: AuthToken | void = await authorizer().login(params)
|
const authResult: AuthToken | void = await authorizer().login(params)
|
||||||
|
|
||||||
if (authResult && authResult.access_token) {
|
if (authResult && authResult.access_token) {
|
||||||
setToken(authResult.access_token)
|
setToken(authResult)
|
||||||
mutate(authResult)
|
mutate(authResult)
|
||||||
loadSubscriptions()
|
loadSubscriptions()
|
||||||
console.debug('signed in')
|
console.debug('signed in')
|
||||||
|
@ -131,7 +125,7 @@ export const SessionProvider = (props: { children: JSX.Element }) => {
|
||||||
const requireAuthentication = async (callback: () => void, modalSource: AuthModalSource) => {
|
const requireAuthentication = async (callback: () => void, modalSource: AuthModalSource) => {
|
||||||
setIsAuthWithCallback(() => callback)
|
setIsAuthWithCallback(() => callback)
|
||||||
|
|
||||||
await loadSession()
|
await authorizer().getProfile()
|
||||||
|
|
||||||
if (!isAuthenticated()) {
|
if (!isAuthenticated()) {
|
||||||
showModal('auth', modalSource)
|
showModal('auth', modalSource)
|
||||||
|
@ -147,7 +141,8 @@ export const SessionProvider = (props: { children: JSX.Element }) => {
|
||||||
const signOut = async () => {
|
const signOut = async () => {
|
||||||
await authorizer().logout()
|
await authorizer().logout()
|
||||||
mutate(null)
|
mutate(null)
|
||||||
resetToken()
|
setToken(null)
|
||||||
|
setUser(null)
|
||||||
setSubscriptions(EMPTY_SUBSCRIPTIONS)
|
setSubscriptions(EMPTY_SUBSCRIPTIONS)
|
||||||
showSnackbar({ body: t("You've successfully logged out") })
|
showSnackbar({ body: t("You've successfully logged out") })
|
||||||
}
|
}
|
||||||
|
@ -155,7 +150,7 @@ export const SessionProvider = (props: { children: JSX.Element }) => {
|
||||||
const confirmEmail = async (input: VerifyEmailInput) => {
|
const confirmEmail = async (input: VerifyEmailInput) => {
|
||||||
const at: void | AuthToken = await authorizer().verifyEmail(input)
|
const at: void | AuthToken = await authorizer().verifyEmail(input)
|
||||||
if (at) {
|
if (at) {
|
||||||
setToken(at.access_token)
|
setToken(at)
|
||||||
mutate(at)
|
mutate(at)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -174,7 +169,6 @@ export const SessionProvider = (props: { children: JSX.Element }) => {
|
||||||
subscriptions,
|
subscriptions,
|
||||||
isSessionLoaded,
|
isSessionLoaded,
|
||||||
author,
|
author,
|
||||||
user,
|
|
||||||
isAuthenticated,
|
isAuthenticated,
|
||||||
actions,
|
actions,
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,7 @@ import {
|
||||||
|
|
||||||
export const inboxClient = {
|
export const inboxClient = {
|
||||||
private: null,
|
private: null,
|
||||||
connect: () => (inboxClient.private = createGraphQLClient('chat')),
|
connect: (token: string) => (inboxClient.private = createGraphQLClient('chat', token)),
|
||||||
|
|
||||||
loadChats: async (options: QueryLoad_ChatsArgs): Promise<Chat[]> => {
|
loadChats: async (options: QueryLoad_ChatsArgs): Promise<Chat[]> => {
|
||||||
const resp = await inboxClient.private.query(myChats, options).toPromise()
|
const resp = await inboxClient.private.query(myChats, options).toPromise()
|
||||||
|
|
|
@ -29,8 +29,8 @@ import draftsLoad from '../query/core/articles-load-drafts'
|
||||||
import myFeed from '../query/core/articles-load-feed'
|
import myFeed from '../query/core/articles-load-feed'
|
||||||
import shoutsLoadSearch from '../query/core/articles-load-search'
|
import shoutsLoadSearch from '../query/core/articles-load-search'
|
||||||
import authorBy from '../query/core/author-by'
|
import authorBy from '../query/core/author-by'
|
||||||
import authorId from '../query/core/author-id'
|
|
||||||
import authorFollowers from '../query/core/author-followers'
|
import authorFollowers from '../query/core/author-followers'
|
||||||
|
import authorId from '../query/core/author-id'
|
||||||
import authorsAll from '../query/core/authors-all'
|
import authorsAll from '../query/core/authors-all'
|
||||||
import authorFollowed from '../query/core/authors-followed-by'
|
import authorFollowed from '../query/core/authors-followed-by'
|
||||||
import authorsLoadBy from '../query/core/authors-load-by'
|
import authorsLoadBy from '../query/core/authors-load-by'
|
||||||
|
@ -45,7 +45,7 @@ const publicGraphQLClient = createGraphQLClient('core')
|
||||||
|
|
||||||
export const apiClient = {
|
export const apiClient = {
|
||||||
private: null,
|
private: null,
|
||||||
connect: () => (apiClient.private = createGraphQLClient('core')), // NOTE: use it after token appears
|
connect: (token: string) => (apiClient.private = createGraphQLClient('core', token)), // NOTE: use it after token appears
|
||||||
|
|
||||||
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()
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
|
import { createGraphQLClient } from '../createGraphQLClient'
|
||||||
import markAllNotificationsAsRead from '../mutation/notifier/mark-all-notifications-as-read'
|
import markAllNotificationsAsRead from '../mutation/notifier/mark-all-notifications-as-read'
|
||||||
import markNotificationAsRead from '../mutation/notifier/mark-notification-as-read'
|
import markNotificationAsRead from '../mutation/notifier/mark-notification-as-read'
|
||||||
import { createGraphQLClient } from '../createGraphQLClient'
|
|
||||||
import loadNotifications from '../query/notifier/notifications-load'
|
import loadNotifications from '../query/notifier/notifications-load'
|
||||||
import { NotificationsResult, QueryLoad_NotificationsArgs } from '../schema/notifier.gen'
|
import { NotificationsResult, QueryLoad_NotificationsArgs } from '../schema/notifier.gen'
|
||||||
|
|
||||||
export const notifierClient = {
|
export const notifierClient = {
|
||||||
private: null,
|
private: null,
|
||||||
connect: () => (notifierClient.private = createGraphQLClient('notifier')),
|
connect: (token: string) => (notifierClient.private = createGraphQLClient('notifier', token)),
|
||||||
|
|
||||||
getNotifications: async (params: QueryLoad_NotificationsArgs): Promise<NotificationsResult> => {
|
getNotifications: async (params: QueryLoad_NotificationsArgs): Promise<NotificationsResult> => {
|
||||||
const resp = await notifierClient.private.query(loadNotifications, params).toPromise()
|
const resp = await notifierClient.private.query(loadNotifications, params).toPromise()
|
||||||
|
|
|
@ -2,7 +2,6 @@ import { ClientOptions, dedupExchange, fetchExchange, Exchange, createClient } f
|
||||||
import { devtoolsExchange } from '@urql/devtools'
|
import { devtoolsExchange } from '@urql/devtools'
|
||||||
|
|
||||||
import { isDev } from '../utils/config'
|
import { isDev } from '../utils/config'
|
||||||
import { getToken } from '../stores/token'
|
|
||||||
|
|
||||||
const exchanges: Exchange[] = [dedupExchange, fetchExchange]
|
const exchanges: Exchange[] = [dedupExchange, fetchExchange]
|
||||||
|
|
||||||
|
@ -10,8 +9,7 @@ if (isDev) {
|
||||||
exchanges.unshift(devtoolsExchange)
|
exchanges.unshift(devtoolsExchange)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createGraphQLClient = (serviceName: string) => {
|
export const createGraphQLClient = (serviceName: string, token: string = '') => {
|
||||||
const token = getToken()
|
|
||||||
const options: ClientOptions = {
|
const options: ClientOptions = {
|
||||||
url: `https://${serviceName}.discours.io`,
|
url: `https://${serviceName}.discours.io`,
|
||||||
maskTypename: true,
|
maskTypename: true,
|
||||||
|
|
|
@ -44,8 +44,8 @@ export const ArticlePage = (props: PageProps) => {
|
||||||
script.dataset.ackeeDomainId = '1004abeb-89b2-4e85-ad97-74f8d2c8ed2d'
|
script.dataset.ackeeDomainId = '1004abeb-89b2-4e85-ad97-74f8d2c8ed2d'
|
||||||
try {
|
try {
|
||||||
document.body.appendChild(script)
|
document.body.appendChild(script)
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
console.warn(err)
|
console.warn(error)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const [scrollToComments, setScrollToComments] = createSignal<boolean>(false)
|
const [scrollToComments, setScrollToComments] = createSignal<boolean>(false)
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
import { cookieStorage, createStorage } from '@solid-primitives/storage'
|
|
||||||
|
|
||||||
// https://start.solidjs.com/api/createCookieSessionStorage
|
|
||||||
export const [store, setStore, { remove }] = createStorage({
|
|
||||||
api: cookieStorage,
|
|
||||||
prefix: 'discoursio',
|
|
||||||
})
|
|
||||||
export const getToken = () => store.token
|
|
||||||
export const setToken = (value) => setStore('token', value)
|
|
||||||
export const resetToken = () => remove('token')
|
|
Loading…
Reference in New Issue
Block a user