diff --git a/src/app.tsx b/src/app.tsx index df984dfd..a05e3cfc 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -7,7 +7,6 @@ import { Loading } from './components/_shared/Loading' import { AuthorsProvider } from './context/authors' import { EditorProvider } from './context/editor' import { FeedProvider } from './context/feed' -import { GraphQLClientProvider } from './context/graphql' import { LocalizeProvider } from './context/localize' import { SessionProvider } from './context/session' import { TopicsProvider } from './context/topics' @@ -18,22 +17,20 @@ export const Providers = (props: { children?: JSX.Element }) => { return ( - - - - - - - - - }>{props.children} - - - - - - - + + + + + + + + }>{props.children} + + + + + + ) diff --git a/src/components/Article/Comment/Comment.tsx b/src/components/Article/Comment/Comment.tsx index 18693fa2..5bb4b6e8 100644 --- a/src/components/Article/Comment/Comment.tsx +++ b/src/components/Article/Comment/Comment.tsx @@ -3,11 +3,12 @@ import { clsx } from 'clsx' import { For, Show, Suspense, createMemo, createSignal, lazy } from 'solid-js' import { Icon } from '~/components/_shared/Icon' import { ShowIfAuthenticated } from '~/components/_shared/ShowIfAuthenticated' -import { useGraphQL } from '~/context/graphql' +import { coreApiUrl } from '~/config' import { useLocalize } from '~/context/localize' import { useReactions } from '~/context/reactions' import { useSession } from '~/context/session' import { useSnackbar, useUI } from '~/context/ui' +import { graphqlClientCreate } from '~/graphql/client' import deleteReactionMutation from '~/graphql/mutation/core/reaction-destroy' import { Author, @@ -49,8 +50,7 @@ export const Comment = (props: Props) => { const { createReaction, updateReaction } = useReactions() const { showConfirm } = useUI() const { showSnackbar } = useSnackbar() - const { mutation } = useGraphQL() - + const client = createMemo(() => graphqlClientCreate(coreApiUrl, session()?.access_token)) const canEdit = createMemo( () => Boolean(author()?.id) && @@ -70,7 +70,9 @@ export const Comment = (props: Props) => { }) if (isConfirmed) { - const resp = await mutation(deleteReactionMutation, { id: props.comment.id }).toPromise() + const resp = await client() + ?.mutation(deleteReactionMutation, { id: props.comment.id }) + .toPromise() const result = resp?.data?.delete_reaction const { error } = result const notificationType = error ? 'error' : 'success' diff --git a/src/components/Author/AuthorRatingControl.tsx b/src/components/Author/AuthorRatingControl.tsx index 5220b9ea..531ec61e 100644 --- a/src/components/Author/AuthorRatingControl.tsx +++ b/src/components/Author/AuthorRatingControl.tsx @@ -1,8 +1,10 @@ import type { Author } from '~/graphql/schema/core.gen' import { clsx } from 'clsx' -import { Show, createSignal } from 'solid-js' -import { useGraphQL } from '~/context/graphql' +import { Show, createMemo, createSignal } from 'solid-js' +import { coreApiUrl } from '~/config' +import { useSession } from '~/context/session' +import { graphqlClientCreate } from '~/graphql/client' import rateAuthorMutation from '~/graphql/mutation/core/author-rate' import styles from './AuthorRatingControl.module.scss' @@ -14,19 +16,24 @@ interface AuthorRatingControlProps { export const AuthorRatingControl = (props: AuthorRatingControlProps) => { const isUpvoted = false const isDownvoted = false + + const { session } = useSession() + const client = createMemo(() => graphqlClientCreate(coreApiUrl, session()?.access_token)) + // eslint-disable-next-line unicorn/consistent-function-scoping const handleRatingChange = async (isUpvote: boolean) => { console.log('handleRatingChange', { isUpvote }) if (props.author?.slug) { const value = isUpvote ? 1 : -1 - const _resp = await mutation(rateAuthorMutation, { - rated_slug: props.author?.slug, - value - }).toPromise() + const _resp = await client() + ?.mutation(rateAuthorMutation, { + rated_slug: props.author?.slug, + value + }) + .toPromise() setRating((r) => (r || 0) + value) } } - const { mutation } = useGraphQL() const [rating, setRating] = createSignal(props.author?.stat?.rating) return (
{ // contexts const { t } = useLocalize() const loc = useLocation() + const { session } = useSession() - const { query } = useGraphQL() + const client = createMemo(() => graphqlClientCreate(coreApiUrl, session()?.access_token)) + const { loadAuthor, authorsEntities } = useAuthors() const { followers: myFollowers, follows: myFollows } = useFollowing() @@ -70,11 +73,15 @@ export const AuthorView = (props: AuthorViewProps) => { setAuthor(foundAuthor) if (foundAuthor) { - const followsResp = await query(getAuthorFollowsQuery, { slug: foundAuthor.slug }).toPromise() + const followsResp = await client() + ?.query(getAuthorFollowsQuery, { slug: foundAuthor.slug }) + .toPromise() const follows = followsResp?.data?.get_author_followers || {} changeFollowing([...(follows?.authors || []), ...(follows?.topics || [])]) - const followersResp = await query(getAuthorFollowersQuery, { slug: foundAuthor.slug }).toPromise() + const followersResp = await client() + ?.query(getAuthorFollowersQuery, { slug: foundAuthor.slug }) + .toPromise() setFollowers(followersResp?.data?.get_author_followers || []) } } diff --git a/src/components/Views/EditView/EditSettingsView.tsx b/src/components/Views/EditView/EditSettingsView.tsx index 72843f2b..45b24127 100644 --- a/src/components/Views/EditView/EditSettingsView.tsx +++ b/src/components/Views/EditView/EditSettingsView.tsx @@ -1,13 +1,15 @@ import { clsx } from 'clsx' import deepEqual from 'fast-deep-equal' -import { Show, createEffect, createSignal, on, onCleanup, onMount } from 'solid-js' +import { Show, createEffect, createMemo, createSignal, on, onCleanup, onMount } from 'solid-js' import { createStore } from 'solid-js/store' import { debounce } from 'throttle-debounce' import { Icon } from '~/components/_shared/Icon' import { InviteMembers } from '~/components/_shared/InviteMembers' +import { coreApiUrl } from '~/config' import { ShoutForm, useEditorContext } from '~/context/editor' -import { useGraphQL } from '~/context/graphql' import { useLocalize } from '~/context/localize' +import { useSession } from '~/context/session' +import { graphqlClientCreate } from '~/graphql/client' import getMyShoutQuery from '~/graphql/query/core/article-my' import type { Shout, Topic } from '~/graphql/schema/core.gen' import { isDesktop } from '~/lib/mediaQuery' @@ -42,7 +44,9 @@ const handleScrollTopButtonClick = (ev: MouseEvent | TouchEvent) => { export const EditSettingsView = (props: Props) => { const { t } = useLocalize() const [isScrolled, setIsScrolled] = createSignal(false) - const { query } = useGraphQL() + const { session } = useSession() + const client = createMemo(() => graphqlClientCreate(coreApiUrl, session()?.access_token)) + const { form, setForm, saveDraft, saveDraftToLocalStorage, getDraftFromLocalStorage } = useEditorContext() const [shoutTopics, setShoutTopics] = createSignal([]) const [draft, setDraft] = createSignal() @@ -106,7 +110,7 @@ export const EditSettingsView = (props: Props) => { () => props.shout?.id, async (shoutId) => { if (shoutId) { - const resp = await query(getMyShoutQuery, { shout_id: shoutId }) + const resp = await client()?.query(getMyShoutQuery, { shout_id: shoutId }) const result = resp?.data?.get_my_shout if (result) { console.debug('[EditView] getMyShout result: ', result) diff --git a/src/components/Views/EditView/EditView.tsx b/src/components/Views/EditView/EditView.tsx index dc7cd775..32c6bbe1 100644 --- a/src/components/Views/EditView/EditView.tsx +++ b/src/components/Views/EditView/EditView.tsx @@ -19,9 +19,11 @@ import { InviteMembers } from '~/components/_shared/InviteMembers' import { Loading } from '~/components/_shared/Loading' import { Popover } from '~/components/_shared/Popover' import { EditorSwiper } from '~/components/_shared/SolidSwiper' +import { coreApiUrl } from '~/config' import { ShoutForm, useEditorContext } from '~/context/editor' -import { useGraphQL } from '~/context/graphql' import { useLocalize } from '~/context/localize' +import { useSession } from '~/context/session' +import { graphqlClientCreate } from '~/graphql/client' import getMyShoutQuery from '~/graphql/query/core/article-my' import type { Shout, Topic } from '~/graphql/schema/core.gen' import { slugify } from '~/intl/translit' @@ -64,7 +66,9 @@ const handleScrollTopButtonClick = (ev: MouseEvent | TouchEvent) => { export const EditView = (props: Props) => { const { t } = useLocalize() const [isScrolled, setIsScrolled] = createSignal(false) - const { query } = useGraphQL() + const { session } = useSession() + const client = createMemo(() => graphqlClientCreate(coreApiUrl, session()?.access_token)) + const { form, formErrors, @@ -140,7 +144,7 @@ export const EditView = (props: Props) => { () => props.shout?.id, async (shoutId) => { if (shoutId) { - const resp = await query(getMyShoutQuery, { shout_id: shoutId }) + const resp = await client()?.query(getMyShoutQuery, { shout_id: shoutId }) const result = resp?.data?.get_my_shout if (result) { console.debug('[EditView] getMyShout result: ', result) diff --git a/src/components/Views/Expo/Expo.tsx b/src/components/Views/Expo/Expo.tsx index 4ccf75a8..1756c444 100644 --- a/src/components/Views/Expo/Expo.tsx +++ b/src/components/Views/Expo/Expo.tsx @@ -5,10 +5,12 @@ import { ConditionalWrapper } from '~/components/_shared/ConditionalWrapper' import { LoadMoreItems, LoadMoreWrapper } from '~/components/_shared/LoadMoreWrapper' import { Loading } from '~/components/_shared/Loading' import { ArticleCardSwiper } from '~/components/_shared/SolidSwiper/ArticleCardSwiper' +import { coreApiUrl } from '~/config' import { useFeed } from '~/context/feed' -import { useGraphQL } from '~/context/graphql' import { useLocalize } from '~/context/localize' +import { useSession } from '~/context/session' import { loadShouts } from '~/graphql/api/public' +import { graphqlClientCreate } from '~/graphql/client' import getRandomTopShoutsQuery from '~/graphql/query/core/articles-load-random-top' import { LoadShoutsFilters, LoadShoutsOptions, Shout } from '~/graphql/schema/core.gen' import { SHOUTS_PER_PAGE } from '~/routes/(main)' @@ -29,7 +31,9 @@ const LOAD_MORE_PAGE_SIZE = 12 export const Expo = (props: Props) => { const { t } = useLocalize() - const { query } = useGraphQL() + const { session } = useSession() + const client = createMemo(() => graphqlClientCreate(coreApiUrl, session()?.access_token)) + const [favoriteTopArticles, setFavoriteTopArticles] = createSignal([]) const [reactedTopMonthArticles, setReactedTopMonthArticles] = createSignal([]) const [expoShouts, setExpoShouts] = createSignal([]) @@ -55,7 +59,7 @@ export const Expo = (props: Props) => { limit: 10, random_limit: 100 } - const resp = await query(getRandomTopShoutsQuery, { options }).toPromise() + const resp = await client()?.query(getRandomTopShoutsQuery, { options }).toPromise() setFavoriteTopArticles(resp?.data?.load_shouts_random_top || []) } @@ -67,7 +71,7 @@ export const Expo = (props: Props) => { limit: 10, random_limit: 10 } - const resp = await query(getRandomTopShoutsQuery, { options }).toPromise() + const resp = await client()?.query(getRandomTopShoutsQuery, { options }).toPromise() setReactedTopMonthArticles(resp?.data?.load_shouts_random_top || []) } diff --git a/src/components/Views/Feed/Feed.tsx b/src/components/Views/Feed/Feed.tsx index c49e6d41..a9d84ff1 100644 --- a/src/components/Views/Feed/Feed.tsx +++ b/src/components/Views/Feed/Feed.tsx @@ -7,14 +7,15 @@ import { Icon } from '~/components/_shared/Icon' import { InviteMembers } from '~/components/_shared/InviteMembers' import { Loading } from '~/components/_shared/Loading' import { ShareModal } from '~/components/_shared/ShareModal' +import { coreApiUrl } from '~/config' import { useAuthors } from '~/context/authors' -import { useGraphQL } from '~/context/graphql' import { useLocalize } from '~/context/localize' import { useReactions } from '~/context/reactions' import { useSession } from '~/context/session' import { useTopics } from '~/context/topics' import { useUI } from '~/context/ui' import { loadUnratedShouts } from '~/graphql/api/private' +import { graphqlClientCreate } from '~/graphql/client' import type { Author, Reaction, Shout } from '~/graphql/schema/core.gen' import { byCreated } from '~/lib/sort' import { FeedSearchParams } from '~/routes/feed/[...order]' @@ -48,10 +49,12 @@ const PERIODS = { export const FeedView = (props: FeedProps) => { const { t } = useLocalize() const loc = useLocation() - const client = useGraphQL() + const { session } = useSession() + const client = createMemo(() => graphqlClientCreate(coreApiUrl, session()?.access_token)) + const unrated = createAsync(async () => { if (client) { - const shoutsLoader = loadUnratedShouts(client, { limit: 5 }) + const shoutsLoader = loadUnratedShouts(client(), { limit: 5 }) return await shoutsLoader() } }) @@ -59,7 +62,6 @@ export const FeedView = (props: FeedProps) => { const { showModal } = useUI() const [isLoading, setIsLoading] = createSignal(false) const [isRightColumnLoaded, setIsRightColumnLoaded] = createSignal(false) - const { session } = useSession() const { loadReactionsBy } = useReactions() const { topTopics } = useTopics() const { topAuthors } = useAuthors() diff --git a/src/context/editor.tsx b/src/context/editor.tsx index 0462db5c..f668e5d4 100644 --- a/src/context/editor.tsx +++ b/src/context/editor.tsx @@ -1,16 +1,18 @@ import { useMatch, useNavigate } from '@solidjs/router' import { Editor } from '@tiptap/core' import type { JSX } from 'solid-js' -import { Accessor, createContext, createSignal, useContext } from 'solid-js' +import { Accessor, createContext, createMemo, createSignal, useContext } from 'solid-js' import { SetStoreFunction, createStore } from 'solid-js/store' +import { coreApiUrl } from '~/config' import { useSnackbar } from '~/context/ui' import deleteShoutQuery from '~/graphql/mutation/core/article-delete' import updateShoutQuery from '~/graphql/mutation/core/article-update' import { Topic, TopicInput } from '~/graphql/schema/core.gen' import { slugify } from '~/intl/translit' import { useFeed } from '../context/feed' -import { useGraphQL } from './graphql' +import { graphqlClientCreate } from '../graphql/client' import { useLocalize } from './localize' +import { useSession } from './session' type WordCounter = { characters: number @@ -82,7 +84,9 @@ export const EditorProvider = (props: { children: JSX.Element }) => { const navigate = useNavigate() const matchEdit = useMatch(() => '/edit') const matchEditSettings = useMatch(() => '/editSettings') - const { mutation } = useGraphQL() + const { session } = useSession() + const client = createMemo(() => graphqlClientCreate(coreApiUrl, session()?.access_token)) + const { addFeed } = useFeed() const snackbar = useSnackbar() const [isEditorPanelVisible, setIsEditorPanelVisible] = createSignal(false) @@ -133,7 +137,7 @@ export const EditorProvider = (props: { children: JSX.Element }) => { console.error(formToUpdate) return { error: 'not enought data' } } - const resp = await mutation(updateShoutQuery, { + const resp = await client()?.mutation(updateShoutQuery, { shout_id: formToUpdate.shoutId, shout_input: { body: formToUpdate.body, @@ -235,7 +239,7 @@ export const EditorProvider = (props: { children: JSX.Element }) => { return } try { - const resp = await mutation(updateShoutQuery, { shout_id, publish: true }).toPromise() + const resp = await client()?.mutation(updateShoutQuery, { shout_id, publish: true }).toPromise() const result = resp?.data?.update_shout if (result) { const { shout: newShout, error } = result @@ -259,7 +263,7 @@ export const EditorProvider = (props: { children: JSX.Element }) => { const deleteShout = async (shout_id: number) => { try { - const resp = await mutation(deleteShoutQuery, { shout_id }).toPromise() + const resp = await client()?.mutation(deleteShoutQuery, { shout_id }).toPromise() return resp?.data?.delete_shout } catch { snackbar?.showSnackbar({ type: 'error', body: localize?.t('Error') || '' }) diff --git a/src/context/feed.tsx b/src/context/feed.tsx index 84a2a697..1ef90e07 100644 --- a/src/context/feed.tsx +++ b/src/context/feed.tsx @@ -1,6 +1,7 @@ import { createLazyMemo } from '@solid-primitives/memo' import { makePersisted } from '@solid-primitives/storage' -import { Accessor, JSX, Setter, createContext, createSignal, useContext } from 'solid-js' +import { Accessor, JSX, Setter, createContext, createMemo, createSignal, useContext } from 'solid-js' +import { coreApiUrl } from '~/config' import { loadFollowedShouts } from '~/graphql/api/private' import { loadShoutsSearch as fetchShoutsSearch, getShout, loadShouts } from '~/graphql/api/public' import { @@ -10,8 +11,9 @@ import { Shout, Topic } from '~/graphql/schema/core.gen' +import { graphqlClientCreate } from '../graphql/client' import { byStat } from '../lib/sort' -import { useGraphQL } from './graphql' +import { useSession } from './session' export const PRERENDERED_ARTICLES_COUNT = 5 export const SHOUTS_PER_PAGE = 20 @@ -172,14 +174,16 @@ export const FeedProvider = (props: { children: JSX.Element }) => { addFeed(result) return { hasMore, newShouts: result } } - const client = useGraphQL() + const { session } = useSession() + const client = createMemo(() => graphqlClientCreate(coreApiUrl, session()?.access_token)) + // Load the user's feed based on the provided options and update the articleEntities and sortedFeed state const loadMyFeed = async ( options: LoadShoutsOptions ): Promise<{ hasMore: boolean; newShouts: Shout[] }> => { if (!options.limit) options.limit = 0 options.limit += 1 - const fetcher = await loadFollowedShouts(client, options) + const fetcher = await loadFollowedShouts(client(), options) const result = (await fetcher()) || [] const hasMore = result.length === options.limit + 1 if (hasMore) result.splice(-1) diff --git a/src/context/following.tsx b/src/context/following.tsx index 15512ef1..69425d1c 100644 --- a/src/context/following.tsx +++ b/src/context/following.tsx @@ -10,11 +10,12 @@ import { } from 'solid-js' import { createStore } from 'solid-js/store' +import { coreApiUrl } from '~/config' import followMutation from '~/graphql/mutation/core/follow' import unfollowMutation from '~/graphql/mutation/core/unfollow' import loadAuthorFollowers from '~/graphql/query/core/author-followers' import { Author, Community, FollowingEntity, Topic } from '~/graphql/schema/core.gen' -import { useGraphQL } from './graphql' +import { graphqlClientCreate } from '../graphql/client' import { useSession } from './session' export type FollowsFilter = 'all' | 'authors' | 'topics' | 'communities' @@ -71,14 +72,14 @@ export const FollowingProvider = (props: { children: JSX.Element }) => { const [follows, setFollows] = createStore(EMPTY_SUBSCRIPTIONS) const { session } = useSession() const authorized = createMemo(() => Boolean(session()?.access_token)) - const { query, mutation } = useGraphQL() + const client = createMemo(() => graphqlClientCreate(coreApiUrl, session()?.access_token)) const fetchData = async () => { setLoading(true) try { if (session()?.access_token) { console.debug('[context.following] fetching subs data...') - const result = await query(loadAuthorFollowers, { user: session()?.user?.id }).toPromise() + const result = await client()?.query(loadAuthorFollowers, { user: session()?.user?.id }).toPromise() if (result) { setFollows((_: AuthorFollowsResult) => { return { ...EMPTY_SUBSCRIPTIONS, ...result } as AuthorFollowsResult @@ -98,7 +99,7 @@ export const FollowingProvider = (props: { children: JSX.Element }) => { if (!authorized()) return setFollowing({ slug, type: 'follow' }) try { - const resp = await mutation(followMutation, { what, slug }).toPromise() + const resp = await client()?.mutation(followMutation, { what, slug }).toPromise() const result = resp?.data?.follow if (!result) return setFollows((subs) => { @@ -117,7 +118,7 @@ export const FollowingProvider = (props: { children: JSX.Element }) => { if (!authorized()) return setFollowing({ slug: slug, type: 'unfollow' }) try { - const resp = await mutation(unfollowMutation, { what, slug }).toPromise() + const resp = await client()?.mutation(unfollowMutation, { what, slug }).toPromise() const result = resp?.data?.unfollow if (!result) return setFollows((subs) => { diff --git a/src/context/graphql.tsx b/src/context/graphql.tsx deleted file mode 100644 index d40f83dc..00000000 --- a/src/context/graphql.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { Client, ClientOptions, cacheExchange, createClient, fetchExchange } from '@urql/core' -import { createContext, createEffect, createSignal, on, useContext } from 'solid-js' -import { JSX } from 'solid-js/jsx-runtime' -import { chatApiUrl, coreApiUrl } from '../config' -import { useSession } from './session' - -type GraphQLClientContextType = Record - -const GraphQLClientContext = createContext({} as GraphQLClientContextType) - -// Function to create a GraphQL client based on the provided URL and token -const graphqlClientCreate = (url: string, token = ''): Client => { - const exchanges = [fetchExchange, cacheExchange] - const options: ClientOptions = { - url, - exchanges - } - - if (token) { - options.fetchOptions = () => ({ - headers: { - 'content-type': 'application/json', - authorization: token - } - }) - } - - return createClient(options) -} - -export const defaultClient = graphqlClientCreate(coreApiUrl) - -export const GraphQLClientProvider = (props: { children?: JSX.Element }) => { - const { session } = useSession() - const [clients, setClients] = createSignal({ [coreApiUrl]: defaultClient }) - - createEffect( - on( - () => session?.()?.access_token || '', - (tkn: string) => { - if (tkn) { - console.info('[context.graphql] authorized client') - setClients(() => ({ - [coreApiUrl]: graphqlClientCreate(coreApiUrl, tkn), - [chatApiUrl]: graphqlClientCreate(chatApiUrl, tkn) - })) - } else { - console.info('[context.graphql] can fetch data') - setClients(() => ({ - [coreApiUrl]: defaultClient - })) - } - }, - { defer: true } - ) - ) - - return {props.children} -} - -export const useGraphQL = (url: string = coreApiUrl) => { - const clients = useContext(GraphQLClientContext) - let c = clients[coreApiUrl] - if (url !== coreApiUrl) { - try { - c = clients[url] - } catch (_e) { - // pass - } - } - if (!c) c = clients[coreApiUrl] - return c -} diff --git a/src/context/inbox.tsx b/src/context/inbox.tsx index b05817ee..c95b4719 100644 --- a/src/context/inbox.tsx +++ b/src/context/inbox.tsx @@ -1,7 +1,7 @@ import type { Accessor, JSX } from 'solid-js' -import { createContext, createSignal, useContext } from 'solid-js' +import { createContext, createMemo, createSignal, useContext } from 'solid-js' import { chatApiUrl } from '~/config' -import { useGraphQL } from '~/context/graphql' +import { graphqlClientCreate } from '~/graphql/client' import createChatMutation from '~/graphql/mutation/chat/chat-create' import createMessageMutation from '~/graphql/mutation/chat/chat-message-create' import loadChatMessagesQuery from '~/graphql/query/chat/chat-messages-load-by' @@ -10,6 +10,7 @@ import type { Chat, Message, MessagesBy, MutationCreate_MessageArgs } from '~/gr import { Author } from '~/graphql/schema/core.gen' import { useAuthors } from '../context/authors' import { SSEMessage, useConnect } from './connect' +import { useSession } from './session' type InboxContextType = { chats: Accessor @@ -37,7 +38,8 @@ export const InboxProvider = (props: { children: JSX.Element }) => { const [chats, setChats] = createSignal([]) const [messages, setMessages] = createSignal([]) const { authorsSorted } = useAuthors() - const { query, mutation } = useGraphQL(chatApiUrl) + const { session } = useSession() + const client = createMemo(() => graphqlClientCreate(chatApiUrl, session()?.access_token)) const handleMessage = (sseMessage: SSEMessage) => { // handling all action types: create update delete join left seen @@ -56,13 +58,13 @@ export const InboxProvider = (props: { children: JSX.Element }) => { addHandler(handleMessage) const loadMessages = async (by: MessagesBy, limit = 50, offset = 0): Promise => { - const resp = await query(loadChatMessagesQuery, { by, limit, offset }) + const resp = await client()?.query(loadChatMessagesQuery, { by, limit, offset }) const result = resp?.data?.load_chat_messages || [] setMessages((mmm) => [...new Set([...mmm, ...result])]) return result } const loadChats = async () => { - const resp = await query(loadChatsQuery, { limit: 50, offset: 0 }).toPromise() + const resp = await client()?.query(loadChatsQuery, { limit: 50, offset: 0 }).toPromise() const result = resp?.data?.load_chats || [] setChats(result) return result @@ -81,7 +83,7 @@ export const InboxProvider = (props: { children: JSX.Element }) => { } const sendMessage = async (args: MutationCreate_MessageArgs) => { - const resp = await mutation(createMessageMutation, args).toPromise() + const resp = await client()?.mutation(createMessageMutation, args).toPromise() const result = resp?.data?.create_message if (result) { const { message, error } = result @@ -100,7 +102,7 @@ export const InboxProvider = (props: { children: JSX.Element }) => { const createChat = async (members: number[], title: string) => { try { - const resp = await mutation(createChatMutation, { members, title }).toPromise() + const resp = await client()?.mutation(createChatMutation, { members, title }).toPromise() const result = resp?.data?.create_chat if (result) { const { chat, error } = result diff --git a/src/context/notifications.tsx b/src/context/notifications.tsx index e989893f..591898f3 100644 --- a/src/context/notifications.tsx +++ b/src/context/notifications.tsx @@ -5,6 +5,8 @@ import { createContext, createMemo, createSignal, onMount, useContext } from 'so import { createStore } from 'solid-js/store' import { Portal } from 'solid-js/web' +import { coreApiUrl } from '~/config' +import { graphqlClientCreate } from '~/graphql/client' import markSeenMutation from '~/graphql/mutation/notifier/mark-seen' import markSeenAfterMutation from '~/graphql/mutation/notifier/mark-seen-after' import markSeenThreadMutation from '~/graphql/mutation/notifier/mark-seen-thread' @@ -13,7 +15,6 @@ import { NotificationGroup, QueryLoad_NotificationsArgs } from '~/graphql/schema import { NotificationsPanel } from '../components/NotificationsPanel' import { ShowIfAuthenticated } from '../components/_shared/ShowIfAuthenticated' import { SSEMessage, useConnect } from './connect' -import { useGraphQL } from './graphql' import { useSession } from './session' type NotificationsContextType = { @@ -49,11 +50,11 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => { const { session } = useSession() const authorized = createMemo(() => Boolean(session()?.access_token)) const { addHandler } = useConnect() - const { query, mutation } = useGraphQL() + const client = createMemo(() => graphqlClientCreate(coreApiUrl, session()?.access_token)) const loadNotificationsGrouped = async (options: QueryLoad_NotificationsArgs) => { if (authorized()) { - const resp = await query(getNotifications, options).toPromise() + const resp = await client()?.query(getNotifications, options).toPromise() const result = resp?.data?.get_notifications const groups = result?.notifications || [] const total = result?.total || 0 @@ -98,7 +99,7 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => { }) const markSeenThread = async (threadId: string) => { - await mutation(markSeenThreadMutation, { threadId }).toPromise() + await client()?.mutation(markSeenThreadMutation, { threadId }).toPromise() const thread = notificationEntities[threadId] thread.seen = true setNotificationEntities((nnn) => ({ ...nnn, [threadId]: thread })) @@ -107,14 +108,14 @@ export const NotificationsProvider = (props: { children: JSX.Element }) => { const markSeenAll = async () => { if (authorized()) { - const _resp = await mutation(markSeenAfterMutation, { after: after() }).toPromise() + const _resp = await client()?.mutation(markSeenAfterMutation, { after: after() }).toPromise() await loadNotificationsGrouped({ after: after() || now, limit: loadedNotificationsCount() }) } } const markSeen = async (notification_id: number) => { if (authorized()) { - await mutation(markSeenMutation, { notification_id }).toPromise() + await client()?.mutation(markSeenMutation, { notification_id }).toPromise() await loadNotificationsGrouped({ after: after() || now, limit: loadedNotificationsCount() }) } } diff --git a/src/context/profile.tsx b/src/context/profile.tsx index 4c5b68b5..7446732a 100644 --- a/src/context/profile.tsx +++ b/src/context/profile.tsx @@ -1,11 +1,21 @@ import type { Author, ProfileInput } from '~/graphql/schema/core.gen' import { AuthToken } from '@authorizerdev/authorizer-js' -import { Accessor, JSX, createContext, createEffect, createSignal, on, useContext } from 'solid-js' +import { + Accessor, + JSX, + createContext, + createEffect, + createMemo, + createSignal, + on, + useContext +} from 'solid-js' import { createStore } from 'solid-js/store' +import { coreApiUrl } from '~/config' import updateAuthorMuatation from '~/graphql/mutation/core/author-update' +import { graphqlClientCreate } from '../graphql/client' import { useAuthors } from './authors' -import { useGraphQL } from './graphql' import { useSession } from './session' type ProfileContextType = { @@ -32,7 +42,7 @@ const userpicUrl = (userpic: string) => { export const ProfileProvider = (props: { children: JSX.Element }) => { const { session } = useSession() - const { mutation } = useGraphQL() + const client = createMemo(() => graphqlClientCreate(coreApiUrl, session()?.access_token)) const { addAuthor } = useAuthors() const [form, setForm] = createStore({} as ProfileInput) const [author, setAuthor] = createSignal({} as Author) @@ -55,7 +65,7 @@ export const ProfileProvider = (props: { children: JSX.Element }) => { ) const submit = async (profile: ProfileInput) => { - const response = await mutation(updateAuthorMuatation, profile).toPromise() + const response = await client()?.mutation(updateAuthorMuatation, profile).toPromise() if (response.error) { console.error(response.error) throw response.error diff --git a/src/context/reactions.tsx b/src/context/reactions.tsx index b31515f8..a0f4ce55 100644 --- a/src/context/reactions.tsx +++ b/src/context/reactions.tsx @@ -1,7 +1,8 @@ import type { JSX } from 'solid-js' -import { createContext, onCleanup, useContext } from 'solid-js' +import { createContext, createMemo, onCleanup, useContext } from 'solid-js' import { createStore, reconcile } from 'solid-js/store' +import { coreApiUrl } from '~/config' import { loadReactions } from '~/graphql/api/public' import createReactionMutation from '~/graphql/mutation/core/reaction-create' import destroyReactionMutation from '~/graphql/mutation/core/reaction-destroy' @@ -13,8 +14,9 @@ import { Reaction, ReactionKind } from '~/graphql/schema/core.gen' -import { useGraphQL } from './graphql' +import { graphqlClientCreate } from '../graphql/client' import { useLocalize } from './localize' +import { useSession } from './session' import { useSnackbar } from './ui' type ReactionsContextType = { @@ -38,7 +40,9 @@ export const ReactionsProvider = (props: { children: JSX.Element }) => { const [reactionsByShout, setReactionsByShout] = createStore>({}) const { t } = useLocalize() const { showSnackbar } = useSnackbar() - const { mutation } = useGraphQL() + const { session } = useSession() + const client = createMemo(() => graphqlClientCreate(coreApiUrl, session()?.access_token)) + const addReactions = (rrr: Reaction[]) => { const newReactionsByShout: Record = { ...reactionsByShout } const newReactionEntities = rrr.reduce( @@ -64,7 +68,7 @@ export const ReactionsProvider = (props: { children: JSX.Element }) => { } const createReaction = async (input: MutationCreate_ReactionArgs): Promise => { - const resp = await mutation(createReactionMutation, input).toPromise() + const resp = await client()?.mutation(createReactionMutation, input).toPromise() const { error, reaction } = resp?.data?.create_reaction || {} if (error) await showSnackbar({ type: 'error', body: t(error) }) if (!reaction) return @@ -96,7 +100,7 @@ export const ReactionsProvider = (props: { children: JSX.Element }) => { reaction_id: number ): Promise<{ error: string; reaction?: string } | null> => { if (reaction_id) { - const resp = await mutation(destroyReactionMutation, { reaction_id }).toPromise() + const resp = await client()?.mutation(destroyReactionMutation, { reaction_id }).toPromise() const result = resp?.data?.destroy_reaction if (!result.error) { setReactionEntities({ @@ -109,7 +113,7 @@ export const ReactionsProvider = (props: { children: JSX.Element }) => { } const updateReaction = async (input: MutationUpdate_ReactionArgs): Promise => { - const resp = await mutation(updateReactionMutation, input).toPromise() + const resp = await client()?.mutation(updateReactionMutation, input).toPromise() const result = resp?.data?.update_reaction if (!result) throw new Error('cannot update reaction') const { error, reaction } = result diff --git a/src/graphql/api/public.ts b/src/graphql/api/public.ts index 02fc58e9..74cfea07 100644 --- a/src/graphql/api/public.ts +++ b/src/graphql/api/public.ts @@ -1,5 +1,5 @@ import { cache } from '@solidjs/router' -import { defaultClient } from '~/context/graphql' +import { defaultClient } from '~/graphql/client' import getShoutQuery from '~/graphql/query/core/article-load' import loadShoutsByQuery from '~/graphql/query/core/articles-load-by' import loadShoutsSearchQuery from '~/graphql/query/core/articles-load-search' diff --git a/src/graphql/client.ts b/src/graphql/client.ts new file mode 100644 index 00000000..30b9ce1f --- /dev/null +++ b/src/graphql/client.ts @@ -0,0 +1,24 @@ +import { Client, ClientOptions, cacheExchange, createClient, fetchExchange } from '@urql/core' +import { coreApiUrl } from '~/config' + +// Функция для создания GraphQL клиента с заданным URL и токеном +export const graphqlClientCreate = (url: string, token = ''): Client => { + const exchanges = [fetchExchange, cacheExchange] + const options: ClientOptions = { + url, + exchanges + } + + if (token) { + options.fetchOptions = () => ({ + headers: { + 'content-type': 'application/json', + authorization: token + } + }) + } + + return createClient(options) +} + +export const defaultClient = graphqlClientCreate(coreApiUrl) diff --git a/src/routes/[slug]/[...tab].tsx b/src/routes/[slug]/[...tab].tsx index df403bd1..8f7ad784 100644 --- a/src/routes/[slug]/[...tab].tsx +++ b/src/routes/[slug]/[...tab].tsx @@ -16,6 +16,7 @@ import AuthorPage, { AuthorPageProps } from '../author/[slug]/[...tab]' import TopicPage, { TopicPageProps } from '../topic/[slug]/[...tab]' const fetchShout = async (slug: string): Promise => { + if (slug.startsWith('@')) return const shoutLoader = getShout({ slug }) const result = await shoutLoader() return result diff --git a/src/routes/author/[slug]/[...tab].tsx b/src/routes/author/[slug]/[...tab].tsx index 01f68af2..fc71c33e 100644 --- a/src/routes/author/[slug]/[...tab].tsx +++ b/src/routes/author/[slug]/[...tab].tsx @@ -1,8 +1,9 @@ import { RouteSectionProps } from '@solidjs/router' -import { ErrorBoundary, createEffect, createMemo, createSignal, on } from 'solid-js' +import { ErrorBoundary, Suspense, createEffect, createMemo, createSignal, on } from 'solid-js' import { AuthorView } from '~/components/Views/Author' import { FourOuFourView } from '~/components/Views/FourOuFour' import { LoadMoreItems, LoadMoreWrapper } from '~/components/_shared/LoadMoreWrapper' +import { Loading } from '~/components/_shared/Loading' import { PageLayout } from '~/components/_shared/PageLayout' import { useAuthors } from '~/context/authors' import { SHOUTS_PER_PAGE, useFeed } from '~/context/feed' @@ -100,24 +101,26 @@ export default function AuthorPage(props: RouteSectionProps) { return ( }> - - - - - - - + }> + + + + + + + + ) } diff --git a/src/routes/edit/(drafts).tsx b/src/routes/edit/(drafts).tsx index 9bb4ec40..3f8b4ca9 100644 --- a/src/routes/edit/(drafts).tsx +++ b/src/routes/edit/(drafts).tsx @@ -1,10 +1,13 @@ import { createAsync } from '@solidjs/router' import { Client } from '@urql/core' +import { createMemo } from 'solid-js' import { AuthGuard } from '~/components/AuthGuard' import { DraftsView } from '~/components/Views/DraftsView' import { PageLayout } from '~/components/_shared/PageLayout' -import { useGraphQL } from '~/context/graphql' +import { coreApiUrl } from '~/config' import { useLocalize } from '~/context/localize' +import { useSession } from '~/context/session' +import { graphqlClientCreate } from '~/graphql/client' import getDraftsQuery from '~/graphql/query/core/articles-load-drafts' import { Shout } from '~/graphql/schema/core.gen' @@ -16,8 +19,9 @@ const fetchDrafts = async (client: Client) => { export default () => { const { t } = useLocalize() - const client = useGraphQL() - const drafts = createAsync(async () => await fetchDrafts(client)) + const { session } = useSession() + const client = createMemo(() => graphqlClientCreate(coreApiUrl, session()?.access_token)) + const drafts = createAsync(async () => await fetchDrafts(client())) return ( diff --git a/src/routes/edit/[id]/(draft).tsx b/src/routes/edit/[id]/(draft).tsx index d4ae45e8..46c22a5e 100644 --- a/src/routes/edit/[id]/(draft).tsx +++ b/src/routes/edit/[id]/(draft).tsx @@ -2,10 +2,11 @@ import { RouteSectionProps, redirect } from '@solidjs/router' import { createEffect, createMemo, createSignal, lazy, on } from 'solid-js' import { AuthGuard } from '~/components/AuthGuard' import { PageLayout } from '~/components/_shared/PageLayout' -import { useGraphQL } from '~/context/graphql' +import { coreApiUrl } from '~/config' import { useLocalize } from '~/context/localize' import { useSession } from '~/context/session' import { useSnackbar } from '~/context/ui' +import { graphqlClientCreate } from '~/graphql/client' import getShoutDraft from '~/graphql/query/core/article-my' import { Shout } from '~/graphql/schema/core.gen' import { LayoutType } from '~/types/common' @@ -38,12 +39,12 @@ export default (props: RouteSectionProps) => { redirect('/edit') // all drafts page } const [shout, setShout] = createSignal() - const client = useGraphQL() + const client = createMemo(() => graphqlClientCreate(coreApiUrl, session()?.access_token)) createEffect(on(session, (s) => s?.access_token && loadDraft(), { defer: true })) const loadDraft = async () => { - const result = await client.query(getShoutDraft, { shout_id: props.params.id }).toPromise() + const result = await client()?.query(getShoutDraft, { shout_id: props.params.id }).toPromise() if (result) { const { shout: loadedShout, error } = result.data.get_my_shout if (error) { diff --git a/src/routes/edit/[id]/settings.tsx b/src/routes/edit/[id]/settings.tsx index 4f0c4ee0..275a7858 100644 --- a/src/routes/edit/[id]/settings.tsx +++ b/src/routes/edit/[id]/settings.tsx @@ -1,22 +1,23 @@ import { RouteSectionProps } from '@solidjs/router' -import { createEffect, createSignal, on } from 'solid-js' +import { createEffect, createMemo, createSignal, on } from 'solid-js' import { AuthGuard } from '~/components/AuthGuard' import EditSettingsView from '~/components/Views/EditView/EditSettingsView' import { PageLayout } from '~/components/_shared/PageLayout' -import { useGraphQL } from '~/context/graphql' +import { coreApiUrl } from '~/config' import { useLocalize } from '~/context/localize' import { useSession } from '~/context/session' +import { graphqlClientCreate } from '~/graphql/client' import getShoutDraft from '~/graphql/query/core/article-my' import { Shout } from '~/graphql/schema/core.gen' export default (props: RouteSectionProps) => { const { t } = useLocalize() - const client = useGraphQL() const { session } = useSession() + const client = createMemo(() => graphqlClientCreate(coreApiUrl, session()?.access_token)) createEffect(on(session, (s) => s?.access_token && loadDraft(), { defer: true })) const [shout, setShout] = createSignal() const loadDraft = async () => { - const result = await client.query(getShoutDraft, { shout_id: props.params.id }).toPromise() + const result = await client()?.query(getShoutDraft, { shout_id: props.params.id }).toPromise() if (result) { const { shout: loadedShout, error } = result.data.get_my_shout if (error) throw new Error(error) diff --git a/src/routes/edit/new.tsx b/src/routes/edit/new.tsx index a0366544..5ae69fdb 100644 --- a/src/routes/edit/new.tsx +++ b/src/routes/edit/new.tsx @@ -1,25 +1,31 @@ import { useNavigate } from '@solidjs/router' import { clsx } from 'clsx' -import { For } from 'solid-js' +import { For, createMemo } from 'solid-js' import { AuthGuard } from '~/components/AuthGuard' import { Button } from '~/components/_shared/Button' import { Icon } from '~/components/_shared/Icon' import { PageLayout } from '~/components/_shared/PageLayout' -import { useGraphQL } from '~/context/graphql' +import { coreApiUrl } from '~/config' import { useLocalize } from '~/context/localize' +import { useSession } from '~/context/session' import { useSnackbar } from '~/context/ui' +import { graphqlClientCreate } from '~/graphql/client' import createShoutMutation from '~/graphql/mutation/core/article-create' import styles from '~/styles/Create.module.scss' import { LayoutType } from '~/types/common' export default () => { const { t } = useLocalize() - const client = useGraphQL() + const { session } = useSession() + const client = createMemo(() => graphqlClientCreate(coreApiUrl, session()?.access_token)) + const { showSnackbar } = useSnackbar() const navigate = useNavigate() const handleCreate = async (layout: LayoutType) => { console.debug('[routes : edit/new] handling create click...') - const result = await client.mutation(createShoutMutation, { shout: { layout: layout } }).toPromise() + const result = await client() + ?.mutation(createShoutMutation, { shout: { layout: layout } }) + .toPromise() if (result) { console.debug(result) const { shout, error } = result.data.create_shout diff --git a/src/routes/feed/my/[...mode]/[...order].tsx b/src/routes/feed/my/[...mode]/[...order].tsx index f3883f88..1eb00be7 100644 --- a/src/routes/feed/my/[...mode]/[...order].tsx +++ b/src/routes/feed/my/[...mode]/[...order].tsx @@ -5,10 +5,11 @@ import { Feed } from '~/components/Views/Feed' import { FeedProps } from '~/components/Views/Feed/Feed' import { LoadMoreItems, LoadMoreWrapper } from '~/components/_shared/LoadMoreWrapper' import { PageLayout } from '~/components/_shared/PageLayout' +import { coreApiUrl } from '~/config' import { useFeed } from '~/context/feed' -import { useGraphQL } from '~/context/graphql' import { useLocalize } from '~/context/localize' import { ReactionsProvider } from '~/context/reactions' +import { useSession } from '~/context/session' import { useTopics } from '~/context/topics' import { loadCoauthoredShouts, @@ -16,6 +17,7 @@ import { loadFollowedShouts, loadUnratedShouts } from '~/graphql/api/private' +import { graphqlClientCreate } from '~/graphql/client' import { LoadShoutsOptions, Shout, Topic } from '~/graphql/schema/core.gen' const feeds = { @@ -54,8 +56,8 @@ export default (props: RouteSectionProps<{ shouts: Shout[]; topics: Topic[] }>) const [searchParams] = useSearchParams() // ?period=month const { t } = useLocalize() const { setFeed, feed } = useFeed() - // TODO: use const { requireAuthentication } = useSession() - const client = useGraphQL() + const { session } = useSession() + const client = createMemo(() => graphqlClientCreate(coreApiUrl, session()?.access_token)) // preload all topics const { addTopics, sortedTopics } = useTopics() @@ -97,7 +99,7 @@ export default (props: RouteSectionProps<{ shouts: Shout[]; topics: Topic[] }>) options.filters = { after: getFromDate(period as FeedPeriod) } } - const shoutsLoader = gqlHandler(client, options) + const shoutsLoader = gqlHandler(client(), options) const loaded = await shoutsLoader() loaded && setFeed((prev: Shout[]) => [...prev, ...loaded]) return loaded as LoadMoreItems