rooting authorizer

This commit is contained in:
Untone 2023-12-14 03:04:07 +03:00
parent dfbbb075e8
commit a59cc9c28e
29 changed files with 87 additions and 134 deletions

View File

@ -4,6 +4,7 @@ import { Meta, MetaProvider } from '@solidjs/meta'
import { Component, createEffect, createMemo } from 'solid-js'
import { Dynamic } from 'solid-js/web'
import { AuthorizerProvider } from '../context/authorizer'
import { ConfirmProvider } from '../context/confirm'
import { ConnectProvider } from '../context/connect'
import { EditorProvider } from '../context/editor'
@ -41,7 +42,6 @@ import { SearchPage } from '../pages/search.page'
import { TopicPage } from '../pages/topic.page'
import { ROUTES, useRouter } from '../stores/router'
import { hideModal, MODALS, showModal } from '../stores/ui'
import { AuthorizerProvider } from '../context/authorizer'
// TODO: lazy load
// const SomePage = lazy(() => import('./Pages/SomePage'))

View File

@ -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 { createPopper } from '@popperjs/core'
@ -12,6 +12,7 @@ import { useReactions } from '../../context/reactions'
import { useSession } from '../../context/session'
import { MediaItem } from '../../pages/types'
import { DEFAULT_HEADER_OFFSET, router, useRouter } from '../../stores/router'
import { capitalize } from '../../utils/capitalize'
import { getImageUrl } from '../../utils/getImageUrl'
import { getDescription } from '../../utils/meta'
import { Icon } from '../_shared/Icon'
@ -33,7 +34,6 @@ import { ShoutRatingControl } from './ShoutRatingControl'
import styles from './Article.module.scss'
import stylesHeader from '../Nav/Header/Header.module.scss'
import { capitalize } from '../../utils/capitalize'
type Props = {
article: Shout
@ -73,7 +73,7 @@ export const FullArticle = (props: Props) => {
const mainTopic = createMemo(() => {
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) {
mt.title = lang() == 'en' ? capitalize(mt.slug.replace('-', ' ')) : mt.title
return mt

View File

@ -32,7 +32,7 @@ export const AuthorBadge = (props: Props) => {
const { changeSearchParam } = useRouter()
const { t, formatDate } = useLocalize()
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) => {

View File

@ -43,7 +43,7 @@ export const AuthorCard = (props: Props) => {
const [subscriptionFilter, setSubscriptionFilter] = createSignal<SubscriptionFilter>('all')
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) => {

View File

@ -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 { clsx } from 'clsx'
@ -87,7 +87,7 @@ export const ArticleCard = (props: ArticleCardProps) => {
const { t, lang, formatDate } = useLocalize()
const { author } = useSession()
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 =
lang() === 'ru' && mainTopic?.title ? mainTopic.title : mainTopicSlug.replace('-', ' ')

View File

@ -3,6 +3,7 @@ import type { ConfirmEmailSearchParams } from './types'
import { clsx } from 'clsx'
import { createMemo, createSignal, onMount, Show } from 'solid-js'
import { useAuthorizer } from '../../../context/authorizer'
import { useLocalize } from '../../../context/localize'
import { useSession } from '../../../context/session'
import { ApiError } from '../../../graphql/error'
@ -14,14 +15,14 @@ import styles from './AuthModal.module.scss'
export const EmailConfirm = () => {
const { t } = useLocalize()
const {
session,
actions: { confirmEmail },
} = useSession()
const [{ user }] = useAuthorizer()
const [isTokenExpired, setIsTokenExpired] = createSignal(false)
const [isTokenInvalid, setIsTokenInvalid] = createSignal(false)
const confirmedEmail = createMemo(() => session()?.user?.email || '')
const confirmedEmail = createMemo(() => user?.email || '')
const { searchParams } = useRouter<ConfirmEmailSearchParams>()

View File

@ -4,10 +4,10 @@ import deepEqual from 'fast-deep-equal'
import { createEffect, createSignal, For, lazy, Match, onCleanup, onMount, Show, Switch } from 'solid-js'
import { createStore } from 'solid-js/store'
import { useAuthorizer } from '../../context/authorizer'
import { useConfirm } from '../../context/confirm'
import { useLocalize } from '../../context/localize'
import { useProfileForm } from '../../context/profile'
import { useSession } from '../../context/session'
import { useSnackbar } from '../../context/snackbar'
import { clone } from '../../utils/clone'
import { getImageUrl } from '../../utils/getImageUrl'
@ -49,9 +49,7 @@ export const ProfileSettings = () => {
actions: { showSnackbar },
} = useSnackbar()
const {
actions: { loadSession },
} = useSession()
const [, { setUser, authorizer }] = useAuthorizer()
const {
actions: { showConfirm },
@ -101,7 +99,10 @@ export const ProfileSettings = () => {
}
showSnackbar({ type: 'error', body: t('Error') })
}
loadSession()
const profile = await authorizer().getProfile()
if (profile) {
setUser(profile)
}
}
const handleCancel = async () => {

View File

@ -84,7 +84,7 @@ export const TopicCard = (props: TopicProps) => {
}
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 (

View File

@ -5,12 +5,12 @@ import { useLocalize } from '../../../context/localize'
import { useSession } from '../../../context/session'
import { FollowingEntity, Topic } from '../../../graphql/schema/core.gen'
import { follow, unfollow } from '../../../stores/zine/common'
import { capitalize } from '../../../utils/capitalize'
import { getImageUrl } from '../../../utils/getImageUrl'
import { Button } from '../../_shared/Button'
import { CheckButton } from '../../_shared/CheckButton'
import styles from './TopicBadge.module.scss'
import { capitalize } from '../../../utils/capitalize'
type Props = {
topic: Topic
@ -54,7 +54,7 @@ export const TopicBadge = (props: Props) => {
/>
<a href={`/topic/${props.topic.slug}`} class={styles.info}>
<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>
<Show
when={props.topic.body}

View File

@ -7,13 +7,13 @@ import { useLocalize } from '../../context/localize'
import { useSession } from '../../context/session'
import { useRouter } from '../../stores/router'
import { setTopicsSort, useTopicsStore } from '../../stores/zine/topics'
import { capitalize } from '../../utils/capitalize'
import { dummyFilter } from '../../utils/dummyFilter'
import { scrollHandler } from '../../utils/scroll'
import { SearchField } from '../_shared/SearchField'
import { TopicCard } from '../Topic/Card'
import styles from './AllTopics.module.scss'
import { capitalize } from '../../utils/capitalize'
type AllTopicsPageSearchParams = {
by: 'shouts' | 'authors' | 'title' | ''
@ -147,7 +147,9 @@ export const AllTopicsView = (props: AllTopicsViewProps) => {
{(topic) => (
<div class={clsx(styles.topic, 'topic col-sm-12 col-md-8')}>
<a href={`/topic/${topic.slug}`}>
{lang() == 'en' ? capitalize(topic.slug.replace(/-/g, ' ')) : topic.title}
{lang() == 'en'
? capitalize(topic.slug.replaceAll('-', ' '))
: topic.title}
</a>
<span class={styles.articlesCounter}>{topic.stat.shouts}</span>
</div>

View File

@ -409,7 +409,7 @@ export const EditView = (props: Props) => {
</form>
</div>
<Show when={page().route === 'editSettings'}>
<PublishSettings shoutId={props.shout.id} form={form}></PublishSettings>
<PublishSettings shoutId={props.shout.id} form={form} />
</Show>
<Panel shoutId={props.shout.id} />
</>

View File

@ -228,7 +228,7 @@ export const FeedView = (props: Props) => {
{(topic) => (
<span class={clsx(stylesTopic.shoutTopic, styles.topic)}>
<a href={`/topic/${topic.slug}`}>
{lang() == 'en' ? topic.slug.replace(/-/g, ' ') : topic.title}
{lang() == 'en' ? topic.slug.replaceAll('-', ' ') : topic.title}
</a>{' '}
</span>
)}

View File

@ -20,7 +20,7 @@ import stylesSettings from '../../../styles/FeedSettings.module.scss'
export const ProfileSubscriptions = () => {
const { t, lang } = useLocalize()
const { session } = useSession()
const { author } = useSession()
const [following, setFollowing] = createSignal<Array<Author | Topic>>([])
const [filtered, setFiltered] = createSignal<Array<Author | Topic>>([])
const [subscriptionFilter, setSubscriptionFilter] = createSignal<SubscriptionFilter>('all')
@ -29,8 +29,8 @@ export const ProfileSubscriptions = () => {
const fetchSubscriptions = async () => {
try {
const [getAuthors, getTopics] = await Promise.all([
apiClient.getAuthorFollowingUsers({ slug: session()?.author.slug }),
apiClient.getAuthorFollowingTopics({ slug: session()?.author.slug }),
apiClient.getAuthorFollowingUsers({ slug: author()?.slug }),
apiClient.getAuthorFollowingTopics({ slug: author()?.slug }),
])
setFollowing([...getAuthors, ...getTopics])
setFiltered([...getAuthors, ...getTopics])

View File

@ -10,6 +10,7 @@ import { Topic } from '../../../graphql/schema/core.gen'
import { UploadedFile } from '../../../pages/types'
import { router } from '../../../stores/router'
import { hideModal, showModal } from '../../../stores/ui'
import { useTopicsStore } from '../../../stores/zine/topics'
import { Button } from '../../_shared/Button'
import { Icon } from '../../_shared/Icon'
import { Image } from '../../_shared/Image'
@ -19,7 +20,6 @@ import { EMPTY_TOPIC } from '../Edit'
import styles from './PublishSettings.module.scss'
import stylesBeside from '../../Feed/Beside.module.scss'
import { useTopicsStore } from '../../../stores/zine/topics'
const SimplifiedEditor = lazy(() => import('../../Editor/SimplifiedEditor'))
const GrowingTextarea = lazy(() => import('../../_shared/GrowingTextarea/GrowingTextarea'))

View File

@ -1,15 +1,7 @@
import type { ParentComponent } from 'solid-js'
import { Authorizer, User, AuthToken, ConfigType } from '@authorizerdev/authorizer-js'
import {
createContext,
createEffect,
createMemo,
createSignal,
onCleanup,
onMount,
useContext,
} from 'solid-js'
import { createContext, createEffect, createMemo, onMount, useContext } from 'solid-js'
import { createStore } from 'solid-js/store'
export type AuthorizerState = {
@ -83,18 +75,6 @@ export const AuthorizerProvider: ParentComponent<AuthorizerProviderProps> = (pro
const handleTokenChange = (token: AuthToken | null) => {
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) => {
@ -110,53 +90,17 @@ export const AuthorizerProvider: ParentComponent<AuthorizerProviderProps> = (pro
setState('user', null)
}
let interval: number | null = null
const interval: number | null = null
const getToken = async () => {
setState('loading', true)
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('loading', false)
}
setState('config', (cfg) => ({ ...cfg, ...metaRes }))
setState('loading', false)
}
onMount(() => {
setState('config', { ...config, redirectURL: window.location.origin + '/?modal=auth' })
!state.token && getToken()
})
onCleanup(() => {
if (interval) {
clearInterval(interval)
}
})
return (

View File

@ -3,8 +3,8 @@ import type { Accessor, JSX } from 'solid-js'
import { fetchEventSource } from '@microsoft/fetch-event-source'
import { createContext, useContext, createSignal, createEffect } from 'solid-js'
import { useSession } from './session'
import { useAuthorizer } from './authorizer'
import { useSession } from './session'
export interface SSEMessage {
id: string

View File

@ -12,6 +12,7 @@ import { slugify } from '../utils/slugify'
import { useLocalize } from './localize'
import { useSnackbar } from './snackbar'
import { useSession } from './session'
type WordCounter = {
characters: number
@ -82,10 +83,13 @@ const removeDraftFromLocalStorage = (shoutId: number) => {
export const EditorProvider = (props: { children: JSX.Element }) => {
const { t } = useLocalize()
const {
actions: { getToken },
} = useSession()
const { page } = useRouter()
const apiClient = createMemo(() => {
if (!coreClient.private) coreClient.connect()
const token = getToken()
if (!coreClient.private) coreClient.connect(token)
return coreClient
})
const {

View File

@ -7,6 +7,7 @@ import { inboxClient } from '../graphql/client/chat'
import { loadMessages } from '../stores/inbox'
import { SSEMessage, useConnect } from './connect'
import { useSession } from './session'
type InboxContextType = {
chats: Accessor<Chat[]>
@ -40,8 +41,12 @@ export const InboxProvider = (props: { children: JSX.Element }) => {
setChats((prev) => [...prev, relivedChat])
}
}
const {
actions: { getToken },
} = useSession()
const apiClient = createMemo(() => {
if (!inboxClient.private) inboxClient.connect()
const token = getToken()
if (!inboxClient.private) inboxClient.connect(token)
return inboxClient
})
const { addHandler } = useConnect()

View File

@ -39,9 +39,13 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => {
const [unreadNotificationsCount, setUnreadNotificationsCount] = createSignal(0)
const [totalNotificationsCount, setTotalNotificationsCount] = createSignal(0)
const [notificationEntities, setNotificationEntities] = createStore<Record<number, Notification>>({})
const { isAuthenticated } = useSession()
const {
isAuthenticated,
actions: { getToken },
} = useSession()
const apiClient = createMemo(() => {
if (!notifierClient.private && isAuthenticated()) notifierClient.connect()
const token = getToken()
if (!notifierClient.private && isAuthenticated()) notifierClient.connect(token)
return notifierClient
})
const { addHandler } = useConnect()

View File

@ -30,13 +30,17 @@ const userpicUrl = (userpic: string) => {
return userpic
}
export const ProfileFormProvider = (props: { children: JSX.Element }) => {
const { author: currentAuthor } = useSession()
const {
author,
actions: { getToken },
} = useSession()
const [form, setForm] = createStore<ProfileInput>({})
const currentSlug = createMemo(() => session()?.user?.slug)
const currentSlug = createMemo(() => author()?.slug)
const apiClient = createMemo(() => {
if (!coreClient.private) coreClient.connect()
const token = getToken()
if (!coreClient.private) coreClient.connect(token)
return coreClient
})

View File

@ -6,6 +6,8 @@ import { createStore, reconcile } from 'solid-js/store'
import { apiClient as coreClient } from '../graphql/client/core'
import { Reaction, ReactionBy, ReactionInput, ReactionKind } from '../graphql/schema/core.gen'
import { useSession } from './session'
type ReactionsContextType = {
reactionEntities: Record<number, Reaction>
actions: {
@ -32,9 +34,13 @@ export function useReactions() {
export const ReactionsProvider = (props: { children: JSX.Element }) => {
const [reactionEntities, setReactionEntities] = createStore<Record<number, Reaction>>({})
const {
actions: { getToken },
} = useSession()
const apiClient = createMemo(() => {
if (!coreClient.private) coreClient.connect()
const token = getToken()
if (!coreClient.private) coreClient.connect(token)
return coreClient
})

View File

@ -3,7 +3,6 @@ import type { Author, Result } from '../graphql/schema/core.gen'
import type { Accessor, JSX, Resource } from 'solid-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 { apiClient } from '../graphql/client/core'
@ -12,13 +11,11 @@ import { showModal } from '../stores/ui'
import { useAuthorizer } from './authorizer'
import { useLocalize } from './localize'
import { useSnackbar } from './snackbar'
import { getToken, resetToken, setToken } from '../stores/token'
export type SessionContextType = {
session: Resource<AuthToken>
isSessionLoaded: Accessor<boolean>
subscriptions: Accessor<Result>
user: Accessor<User>
author: Resource<Author | null>
isAuthenticated: Accessor<boolean>
actions: {
@ -53,8 +50,8 @@ export const SessionProvider = (props: { children: JSX.Element }) => {
const {
actions: { showSnackbar },
} = useSnackbar()
const [, { authorizer }] = useAuthorizer()
const [{ token }, { setUser, setToken, authorizer }] = useAuthorizer()
const getToken = () => token.access_token
const loadSubscriptions = async (): Promise<void> => {
const result = await apiClient.getMySubscriptions()
if (result) {
@ -66,14 +63,12 @@ export const SessionProvider = (props: { children: JSX.Element }) => {
const getSession = async (): Promise<AuthToken> => {
try {
const token = getToken()
if (token) {
const authResult = await authorizer().getSession({
Authorization: token,
})
const authResult = await authorizer().getSession()
if (authResult && authResult.access_token) {
console.log(authResult)
setToken(authResult.access_token)
setToken(authResult)
if (authResult.user) setUser(authResult.user)
loadSubscriptions()
return authResult
}
@ -81,7 +76,8 @@ export const SessionProvider = (props: { children: JSX.Element }) => {
return null
} catch (error) {
console.error('getSession error:', error)
resetToken()
setToken(null)
setUser(null)
return null
} finally {
setTimeout(() => {
@ -95,8 +91,6 @@ export const SessionProvider = (props: { children: JSX.Element }) => {
initialValue: null,
})
const user = createMemo(() => session()?.user)
const [author, { refetch: loadAuthor }] = createResource<Author | null>(
async () => {
const u = session()?.user
@ -117,7 +111,7 @@ export const SessionProvider = (props: { children: JSX.Element }) => {
const authResult: AuthToken | void = await authorizer().login(params)
if (authResult && authResult.access_token) {
setToken(authResult.access_token)
setToken(authResult)
mutate(authResult)
loadSubscriptions()
console.debug('signed in')
@ -131,7 +125,7 @@ export const SessionProvider = (props: { children: JSX.Element }) => {
const requireAuthentication = async (callback: () => void, modalSource: AuthModalSource) => {
setIsAuthWithCallback(() => callback)
await loadSession()
await authorizer().getProfile()
if (!isAuthenticated()) {
showModal('auth', modalSource)
@ -147,7 +141,8 @@ export const SessionProvider = (props: { children: JSX.Element }) => {
const signOut = async () => {
await authorizer().logout()
mutate(null)
resetToken()
setToken(null)
setUser(null)
setSubscriptions(EMPTY_SUBSCRIPTIONS)
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 at: void | AuthToken = await authorizer().verifyEmail(input)
if (at) {
setToken(at.access_token)
setToken(at)
mutate(at)
}
}
@ -174,7 +169,6 @@ export const SessionProvider = (props: { children: JSX.Element }) => {
subscriptions,
isSessionLoaded,
author,
user,
isAuthenticated,
actions,
}

View File

@ -26,7 +26,7 @@ import {
export const inboxClient = {
private: null,
connect: () => (inboxClient.private = createGraphQLClient('chat')),
connect: (token: string) => (inboxClient.private = createGraphQLClient('chat', token)),
loadChats: async (options: QueryLoad_ChatsArgs): Promise<Chat[]> => {
const resp = await inboxClient.private.query(myChats, options).toPromise()

View File

@ -29,8 +29,8 @@ import draftsLoad from '../query/core/articles-load-drafts'
import myFeed from '../query/core/articles-load-feed'
import shoutsLoadSearch from '../query/core/articles-load-search'
import authorBy from '../query/core/author-by'
import authorId from '../query/core/author-id'
import authorFollowers from '../query/core/author-followers'
import authorId from '../query/core/author-id'
import authorsAll from '../query/core/authors-all'
import authorFollowed from '../query/core/authors-followed-by'
import authorsLoadBy from '../query/core/authors-load-by'
@ -45,7 +45,7 @@ const publicGraphQLClient = createGraphQLClient('core')
export const apiClient = {
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 }) => {
const response = await publicGraphQLClient.query(topicsRandomQuery, { amount }).toPromise()

View File

@ -1,12 +1,12 @@
import { createGraphQLClient } from '../createGraphQLClient'
import markAllNotificationsAsRead from '../mutation/notifier/mark-all-notifications-as-read'
import markNotificationAsRead from '../mutation/notifier/mark-notification-as-read'
import { createGraphQLClient } from '../createGraphQLClient'
import loadNotifications from '../query/notifier/notifications-load'
import { NotificationsResult, QueryLoad_NotificationsArgs } from '../schema/notifier.gen'
export const notifierClient = {
private: null,
connect: () => (notifierClient.private = createGraphQLClient('notifier')),
connect: (token: string) => (notifierClient.private = createGraphQLClient('notifier', token)),
getNotifications: async (params: QueryLoad_NotificationsArgs): Promise<NotificationsResult> => {
const resp = await notifierClient.private.query(loadNotifications, params).toPromise()

View File

@ -2,7 +2,6 @@ import { ClientOptions, dedupExchange, fetchExchange, Exchange, createClient } f
import { devtoolsExchange } from '@urql/devtools'
import { isDev } from '../utils/config'
import { getToken } from '../stores/token'
const exchanges: Exchange[] = [dedupExchange, fetchExchange]
@ -10,8 +9,7 @@ if (isDev) {
exchanges.unshift(devtoolsExchange)
}
export const createGraphQLClient = (serviceName: string) => {
const token = getToken()
export const createGraphQLClient = (serviceName: string, token: string = '') => {
const options: ClientOptions = {
url: `https://${serviceName}.discours.io`,
maskTypename: true,

View File

@ -44,8 +44,8 @@ export const ArticlePage = (props: PageProps) => {
script.dataset.ackeeDomainId = '1004abeb-89b2-4e85-ad97-74f8d2c8ed2d'
try {
document.body.appendChild(script)
} catch (err) {
console.warn(err)
} catch (error) {
console.warn(error)
}
})
const [scrollToComments, setScrollToComments] = createSignal<boolean>(false)

View File

@ -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')

View File