signIn/getSession optimizaiton frontend (#272)

Co-authored-by: Igor Lobanov <igor.lobanov@onetwotrip.com>
This commit is contained in:
Ilya Y 2023-10-19 18:05:22 +03:00 committed by GitHub
parent 85e8533931
commit 891d29ff6a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 109 additions and 89 deletions

View File

@ -17,13 +17,14 @@ export const AuthorBadge = (props: Props) => {
const [isSubscribing, setIsSubscribing] = createSignal(false)
const {
session,
actions: { loadSession, requireAuthentication }
subscriptions,
actions: { loadSubscriptions, requireAuthentication }
} = useSession()
const { t, formatDate } = useLocalize()
const subscribed = createMemo<boolean>(() => {
return session()?.news?.authors?.some((u) => u === props.author.slug) || false
})
const subscribed = createMemo(() =>
subscriptions().authors.some((author) => author.slug === props.author.slug)
)
const subscribe = async (really = true) => {
setIsSubscribing(true)
@ -32,7 +33,7 @@ export const AuthorBadge = (props: Props) => {
? follow({ what: FollowingEntity.Author, slug: props.author.slug })
: unfollow({ what: FollowingEntity.Author, slug: props.author.slug }))
await loadSession()
await loadSubscriptions()
setIsSubscribing(false)
}
const handleSubscribe = (really: boolean) => {

View File

@ -50,8 +50,9 @@ export const AuthorCard = (props: Props) => {
const { t, lang } = useLocalize()
const {
session,
subscriptions,
isSessionLoaded,
actions: { loadSession, requireAuthentication }
actions: { loadSubscriptions, requireAuthentication }
} = useSession()
const [isSubscribing, setIsSubscribing] = createSignal(false)
@ -59,9 +60,9 @@ export const AuthorCard = (props: Props) => {
const [subscriptionFilter, setSubscriptionFilter] = createSignal<SubscriptionFilter>('all')
const [userpicUrl, setUserpicUrl] = createSignal<string>()
const subscribed = createMemo<boolean>(() => {
return session()?.news?.authors?.some((u) => u === props.author.slug) || false
})
const subscribed = createMemo<boolean>(() =>
subscriptions().authors.some((author) => author.slug === props.author.slug)
)
const subscribe = async (really = true) => {
setIsSubscribing(true)
@ -70,7 +71,7 @@ export const AuthorCard = (props: Props) => {
? follow({ what: FollowingEntity.Author, slug: props.author.slug })
: unfollow({ what: FollowingEntity.Author, slug: props.author.slug }))
await loadSession()
await loadSubscriptions()
setIsSubscribing(false)
}

View File

@ -20,7 +20,7 @@ type FeedSidebarProps = {
export const Sidebar = (props: FeedSidebarProps) => {
const { t } = useLocalize()
const { seen } = useSeenStore()
const { session } = useSession()
const { subscriptions } = useSession()
const { page } = useRouter()
const { authorEntities } = useAuthorsStore({ authors: props.authors })
const { articlesByTopic } = useArticlesStore()
@ -118,7 +118,7 @@ export const Sidebar = (props: FeedSidebarProps) => {
</li>
</ul>
<Show when={session()?.news?.authors || session()?.news?.topics}>
<Show when={subscriptions().authors.length > 0 || subscriptions().topics.length > 0}>
<h4
classList={{ [styles.opened]: isSubscriptionsVisible() }}
onClick={() => {
@ -129,39 +129,31 @@ export const Sidebar = (props: FeedSidebarProps) => {
</h4>
<ul class={clsx(styles.subscriptions, { [styles.hidden]: !isSubscriptionsVisible() })}>
<For each={session()?.news?.authors}>
{(authorSlug: string) => (
<For each={subscriptions().authors}>
{(author) => (
<li>
<a
href={`/author/${authorSlug}`}
classList={{ [styles.unread]: checkAuthorIsSeen(authorSlug) }}
href={`/author/${author.slug}`}
classList={{ [styles.unread]: checkAuthorIsSeen(author.slug) }}
>
<div class={styles.sidebarItemName}>
<Show when={authorEntities()[authorSlug]}>
<Userpic
name={authorEntities()[authorSlug].name}
userpic={authorEntities()[authorSlug].userpic}
/>
</Show>
<Show when={!authorEntities()[authorSlug]}>
<Icon name="hash" class={styles.icon} />
</Show>
{authorEntities()[authorSlug]?.name}
<Userpic name={author.name} userpic={author.userpic} />
{author.name}
</div>
</a>
</li>
)}
</For>
<For each={session()?.news?.topics}>
{(topicSlug: string) => (
<For each={subscriptions().topics}>
{(topic) => (
<li>
<a
href={`/topic/${topicSlug}`}
classList={{ [styles.unread]: checkTopicIsSeen(topicSlug) }}
href={`/topic/${topic.slug}`}
classList={{ [styles.unread]: checkTopicIsSeen(topic.slug) }}
>
<div class={styles.sidebarItemName}>
<Icon name="hash" class={styles.icon} />
{topicEntities()[topicSlug]?.title ?? topicSlug}
{topic.title}
</div>
</a>
</li>

View File

@ -186,12 +186,8 @@ export const HeaderAuth = (props: Props) => {
<a href="/inbox">
{/*FIXME: replace with route*/}
<div classList={{ entered: page().path === '/inbox' }}>
<Icon name="inbox-white" counter={session()?.news?.unread || 0} class={styles.icon} />
<Icon
name="inbox-white-hover"
counter={session()?.news?.unread || 0}
class={clsx(styles.icon, styles.iconHover)}
/>
<Icon name="inbox-white" class={styles.icon} />
<Icon name="inbox-white-hover" class={clsx(styles.icon, styles.iconHover)} />
</div>
</a>
</div>

View File

@ -34,19 +34,15 @@ interface TopicProps {
export const TopicCard = (props: TopicProps) => {
const { t } = useLocalize()
const {
session,
subscriptions,
isSessionLoaded,
actions: { loadSession, requireAuthentication }
actions: { loadSubscriptions, requireAuthentication }
} = useSession()
const [isSubscribing, setIsSubscribing] = createSignal(false)
const subscribed = createMemo(() => {
if (!session()?.user?.slug || !session()?.news?.topics) {
return false
}
return session()?.news.topics.includes(props.topic.slug)
return subscriptions().topics.some((topic) => topic.slug === props.topic.slug)
})
const subscribe = async (really = true) => {
@ -56,7 +52,7 @@ export const TopicCard = (props: TopicProps) => {
? follow({ what: FollowingEntity.Topic, slug: props.topic.slug })
: unfollow({ what: FollowingEntity.Topic, slug: props.topic.slug }))
await loadSession()
await loadSubscriptions()
setIsSubscribing(false)
}

View File

@ -14,11 +14,15 @@ type Props = {
export const FullTopic = (props: Props) => {
const {
session,
subscriptions,
actions: { requireAuthentication }
} = useSession()
const { t } = useLocalize()
const subscribed = createMemo(() => session()?.news?.topics?.includes(props.topic?.slug))
const subscribed = createMemo(() =>
subscriptions().topics.some((topic) => topic.slug === props.topic?.slug)
)
const handleSubscribe = (isFollowed: boolean) => {
requireAuthentication(() => {

View File

@ -19,17 +19,13 @@ export const TopicBadge = (props: Props) => {
const { t } = useLocalize()
const {
isAuthenticated,
session,
actions: { loadSession }
subscriptions,
actions: { loadSubscriptions }
} = useSession()
const subscribed = createMemo(() => {
if (!session()?.user?.slug || !session()?.news?.topics) {
return false
}
return session()?.news.topics.includes(props.topic.slug)
})
const subscribed = createMemo(() =>
subscriptions().topics.some((topic) => topic.slug === props.topic.slug)
)
const subscribe = async (really = true) => {
setIsSubscribing(true)
@ -38,7 +34,7 @@ export const TopicBadge = (props: Props) => {
? follow({ what: FollowingEntity.Topic, slug: props.topic.slug })
: unfollow({ what: FollowingEntity.Topic, slug: props.topic.slug }))
await loadSession()
await loadSubscriptions()
setIsSubscribing(false)
}

View File

@ -35,7 +35,7 @@ export const AllAuthorsView = (props: AllAuthorsViewProps) => {
const [searchQuery, setSearchQuery] = createSignal('')
const { session } = useSession()
const { session, subscriptions } = useSession()
onMount(() => {
if (!searchParams().by) {
@ -72,7 +72,8 @@ export const AllAuthorsView = (props: AllAuthorsViewProps) => {
return keys
})
const subscribed = (s) => Boolean(session()?.news?.authors && session()?.news?.authors?.includes(s || ''))
const subscribed = (authorSlug: string) =>
subscriptions().authors.some((author) => author.slug === authorSlug)
const filteredAuthors = createMemo(() => {
return dummyFilter(sortedAuthors(), searchQuery(), lang())

View File

@ -34,7 +34,7 @@ export const AllTopicsView = (props: AllTopicsViewProps) => {
sortBy: searchParams().by || 'shouts'
})
const { session } = useSession()
const { session, subscriptions } = useSession()
onMount(() => {
if (!searchParams().by) {
@ -68,7 +68,7 @@ export const AllTopicsView = (props: AllTopicsViewProps) => {
return keys
})
const subscribed = (s) => Boolean(session()?.news?.topics && session()?.news?.topics?.includes(s || ''))
const subscribed = (topicSlug: string) => subscriptions().topics.some((topic) => topic.slug === topicSlug)
const showMore = () => setLimit((oldLimit) => oldLimit + PAGE_SIZE)
const [searchQuery, setSearchQuery] = createSignal('')

View File

@ -8,7 +8,7 @@ import {
onMount,
useContext
} from 'solid-js'
import type { AuthResult, User } from '../graphql/types.gen'
import type { AuthResult, MySubscriptionsQueryResult, User } from '../graphql/types.gen'
import { apiClient } from '../utils/apiClient'
import { resetToken, setToken } from '../graphql/privateGraphQLClient'
import { useSnackbar } from './snackbar'
@ -19,10 +19,12 @@ import type { AuthModalSource } from '../components/Nav/AuthModal/types'
type SessionContextType = {
session: Resource<AuthResult>
isSessionLoaded: Accessor<boolean>
subscriptions: Accessor<MySubscriptionsQueryResult>
user: Accessor<User>
isAuthenticated: Accessor<boolean>
actions: {
loadSession: () => AuthResult | Promise<AuthResult>
loadSubscriptions: () => Promise<void>
requireAuthentication: (
callback: (() => Promise<void>) | (() => void),
modalSource: AuthModalSource
@ -41,6 +43,10 @@ export function useSession() {
export const SessionProvider = (props: { children: JSX.Element }) => {
const [isSessionLoaded, setIsSessionLoaded] = createSignal(false)
const [subscriptions, setSubscriptions] = createSignal<MySubscriptionsQueryResult>({
topics: [],
authors: []
})
const { t } = useLocalize()
const {
actions: { showSnackbar }
@ -53,6 +59,7 @@ export const SessionProvider = (props: { children: JSX.Element }) => {
return null
}
setToken(authResult.token)
loadSubscriptions()
return authResult
} catch (error) {
console.error('getSession error:', error)
@ -63,6 +70,11 @@ export const SessionProvider = (props: { children: JSX.Element }) => {
}
}
const loadSubscriptions = async (): Promise<void> => {
const result = await apiClient.getMySubscriptions()
setSubscriptions(result)
}
const [session, { refetch: loadSession, mutate }] = createResource<AuthResult>(getSession, {
ssrLoadFrom: 'initial',
initialValue: null
@ -76,7 +88,8 @@ export const SessionProvider = (props: { children: JSX.Element }) => {
const authResult = await apiClient.authLogin({ email, password })
setToken(authResult.token)
mutate(authResult)
console.debug('signed in')
loadSubscriptions()
// console.debug('signed in')
}
const [isAuthWithCallback, setIsAuthWithCallback] = createSignal(null)
@ -119,10 +132,18 @@ export const SessionProvider = (props: { children: JSX.Element }) => {
requireAuthentication,
signIn,
signOut,
confirmEmail
confirmEmail,
loadSubscriptions
}
const value: SessionContextType = { session, isSessionLoaded, user, isAuthenticated, actions }
const value: SessionContextType = {
session,
subscriptions,
isSessionLoaded,
user,
isAuthenticated,
actions
}
onMount(() => {
loadSession()

View File

@ -14,13 +14,6 @@ export default gql`
userpic
links
}
news {
unread
topics
authors
reactions
communities
}
}
}
`

View File

@ -13,13 +13,6 @@ export default gql`
userpic
links
}
news {
unread
topics
authors
reactions
# communities
}
}
}
`

View File

@ -0,0 +1,21 @@
import { gql } from '@urql/core'
export default gql`
query MySubscriptionsQuery {
loadMySubscriptions {
topics {
id
title
body
slug
}
authors {
id
name
slug
userpic
createdAt
}
}
}
`

View File

@ -16,7 +16,6 @@ export type Scalars = {
export type AuthResult = {
error?: Maybe<Scalars['String']>
news?: Maybe<UserFollowings>
token?: Maybe<Scalars['String']>
user?: Maybe<User>
}
@ -302,6 +301,11 @@ export type MutationUpdateTopicArgs = {
input: TopicInput
}
export type MySubscriptionsQueryResult = {
authors: Array<Maybe<Author>>
topics: Array<Maybe<Topic>>
}
export type Notification = {
createdAt: Scalars['DateTime']
data?: Maybe<Scalars['String']>
@ -357,6 +361,7 @@ export type Query = {
loadChats: Result
loadDrafts: Array<Maybe<Shout>>
loadMessagesBy: Result
loadMySubscriptions?: Maybe<MySubscriptionsQueryResult>
loadNotifications: NotificationsQueryResult
loadReactionsBy: Array<Maybe<Reaction>>
loadRecipients: Result
@ -690,11 +695,3 @@ export type User = {
username: Scalars['String']
userpic?: Maybe<Scalars['String']>
}
export type UserFollowings = {
authors?: Maybe<Array<Maybe<Scalars['String']>>>
communities?: Maybe<Array<Maybe<Scalars['String']>>>
reactions?: Maybe<Array<Maybe<Scalars['Int']>>>
topics?: Maybe<Array<Maybe<Scalars['String']>>>
unread?: Maybe<Scalars['Int']>
}

View File

@ -17,7 +17,8 @@ import type {
ReactionBy,
Shout,
NotificationsQueryParams,
NotificationsQueryResult
NotificationsQueryResult,
MySubscriptionsQueryResult
} from '../graphql/types.gen'
import { publicGraphQLClient } from '../graphql/publicGraphQLClient'
import { getToken, privateGraphQLClient } from '../graphql/privateGraphQLClient'
@ -58,6 +59,7 @@ import updateArticle from '../graphql/mutation/article-update'
import deleteShout from '../graphql/mutation/article-delete'
import notifications from '../graphql/query/notifications'
import markNotificationAsRead from '../graphql/mutation/mark-notification-as-read'
import mySubscriptions from '../graphql/query/my-subscriptions'
type ApiErrorCode =
| 'unknown'
@ -366,6 +368,12 @@ export const apiClient = {
.toPromise()
},
getMySubscriptions: async (): Promise<MySubscriptionsQueryResult> => {
const resp = await privateGraphQLClient.query(mySubscriptions, {}).toPromise()
// console.debug(resp.data)
return resp.data.loadMySubscriptions
},
// inbox
getChats: async (options: QueryLoadChatsArgs): Promise<Chat[]> => {
const resp = await privateGraphQLClient.query(myChats, options).toPromise()