code review

This commit is contained in:
Igor Lobanov 2022-11-14 11:02:08 +01:00
parent 269f7328d8
commit 338d9e3d08
23 changed files with 85 additions and 78 deletions

View File

@ -10,7 +10,7 @@ import { showModal } from '../../stores/ui'
import { incrementView } from '../../stores/zine/articles' import { incrementView } from '../../stores/zine/articles'
import MD from './MD' import MD from './MD'
import { SharePopup } from './SharePopup' import { SharePopup } from './SharePopup'
import { useAuth } from '../../context/auth' import { useSession } from '../../context/session'
const MAX_COMMENT_LEVEL = 6 const MAX_COMMENT_LEVEL = 6
@ -38,7 +38,7 @@ const formatDate = (date: Date) => {
} }
export const FullArticle = (props: ArticleProps) => { export const FullArticle = (props: ArticleProps) => {
const { session } = useAuth() const { session } = useSession()
onMount(() => { onMount(() => {
incrementView({ articleSlug: props.article.slug }) incrementView({ articleSlug: props.article.slug })

View File

@ -1,7 +1,9 @@
import { Icon } from '../Nav/Icon' import { Icon } from '../Nav/Icon'
import styles from '../Nav/Popup.module.scss'
import { t } from '../../utils/intl' import { t } from '../../utils/intl'
import { Popup, PopupProps } from '../Nav/Popup'
import styles from '../_shared/Popup.module.scss'
import type { PopupProps } from '../_shared/Popup'
import { Popup } from '../_shared/Popup'
type SharePopupProps = Omit<PopupProps, 'children'> type SharePopupProps = Omit<PopupProps, 'children'>

View File

@ -8,7 +8,7 @@ import { t } from '../../utils/intl'
import { locale } from '../../stores/ui' import { locale } from '../../stores/ui'
import { follow, unfollow } from '../../stores/zine/common' import { follow, unfollow } from '../../stores/zine/common'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import { useAuth } from '../../context/auth' import { useSession } from '../../context/session'
interface AuthorCardProps { interface AuthorCardProps {
compact?: boolean compact?: boolean
@ -23,7 +23,7 @@ interface AuthorCardProps {
} }
export const AuthorCard = (props: AuthorCardProps) => { export const AuthorCard = (props: AuthorCardProps) => {
const { session } = useAuth() const { session } = useSession()
const subscribed = createMemo<boolean>( const subscribed = createMemo<boolean>(
() => session()?.news?.authors?.some((u) => u === props.author.slug) || false () => session()?.news?.authors?.some((u) => u === props.author.slug) || false

View File

@ -6,7 +6,7 @@ import { Icon } from '../Nav/Icon'
import { useTopicsStore } from '../../stores/zine/topics' import { useTopicsStore } from '../../stores/zine/topics'
import { useArticlesStore } from '../../stores/zine/articles' import { useArticlesStore } from '../../stores/zine/articles'
import { useSeenStore } from '../../stores/zine/seen' import { useSeenStore } from '../../stores/zine/seen'
import { useAuth } from '../../context/auth' import { useSession } from '../../context/session'
type FeedSidebarProps = { type FeedSidebarProps = {
authors: Author[] authors: Author[]
@ -14,7 +14,7 @@ type FeedSidebarProps = {
export const FeedSidebar = (props: FeedSidebarProps) => { export const FeedSidebar = (props: FeedSidebarProps) => {
const { getSeen: seen } = useSeenStore() const { getSeen: seen } = useSeenStore()
const { session } = useAuth() const { session } = useSession()
const { authorEntities } = useAuthorsStore({ authors: props.authors }) const { authorEntities } = useAuthorsStore({ authors: props.authors })
const { articlesByTopic } = useArticlesStore() const { articlesByTopic } = useArticlesStore()
const { topicEntities } = useTopicsStore() const { topicEntities } = useTopicsStore()

View File

@ -5,14 +5,14 @@ import { hideModal } from '../../../stores/ui'
import { createMemo, createSignal, onMount, Show } from 'solid-js' import { createMemo, createSignal, onMount, Show } from 'solid-js'
import { handleClientRouteLinkClick, useRouter } from '../../../stores/router' import { handleClientRouteLinkClick, useRouter } from '../../../stores/router'
import type { ConfirmEmailSearchParams } from './types' import type { ConfirmEmailSearchParams } from './types'
import { useAuth } from '../../../context/auth'
import { ApiError } from '../../../utils/apiClient' import { ApiError } from '../../../utils/apiClient'
import { useSession } from '../../../context/session'
export const EmailConfirm = () => { export const EmailConfirm = () => {
const { const {
session, session,
actions: { confirmEmail } actions: { confirmEmail }
} = useAuth() } = useSession()
const [isTokenExpired, setIsTokenExpired] = createSignal(false) const [isTokenExpired, setIsTokenExpired] = createSignal(false)
const [isTokenInvalid, setIsTokenInvalid] = createSignal(false) const [isTokenInvalid, setIsTokenInvalid] = createSignal(false)

View File

@ -7,8 +7,8 @@ import { email, setEmail } from './sharedLogic'
import type { AuthModalSearchParams } from './types' import type { AuthModalSearchParams } from './types'
import { isValidEmail } from './validators' import { isValidEmail } from './validators'
import { locale } from '../../../stores/ui' import { locale } from '../../../stores/ui'
import { signSendLink } from '../../../context/auth'
import { ApiError } from '../../../utils/apiClient' import { ApiError } from '../../../utils/apiClient'
import { signSendLink } from '../../../stores/auth'
type FormFields = { type FormFields = {
email: string email: string

View File

@ -9,7 +9,8 @@ import { email, setEmail } from './sharedLogic'
import { useRouter } from '../../../stores/router' import { useRouter } from '../../../stores/router'
import type { AuthModalSearchParams } from './types' import type { AuthModalSearchParams } from './types'
import { hideModal, locale } from '../../../stores/ui' import { hideModal, locale } from '../../../stores/ui'
import { signSendLink, useAuth } from '../../../context/auth' import { useSession } from '../../../context/session'
import { signSendLink } from '../../../stores/auth'
type FormFields = { type FormFields = {
email: string email: string
@ -28,7 +29,7 @@ export const LoginForm = () => {
const { const {
actions: { signIn } actions: { signIn }
} = useAuth() } = useSession()
const { changeSearchParam } = useRouter<AuthModalSearchParams>() const { changeSearchParam } = useRouter<AuthModalSearchParams>()

View File

@ -11,7 +11,7 @@ import { useRouter } from '../../../stores/router'
import type { AuthModalSearchParams } from './types' import type { AuthModalSearchParams } from './types'
import { hideModal } from '../../../stores/ui' import { hideModal } from '../../../stores/ui'
import { checkEmail, useEmailChecks } from '../../../stores/emailChecks' import { checkEmail, useEmailChecks } from '../../../stores/emailChecks'
import { register } from '../../../context/auth' import { register } from '../../../stores/auth'
type FormFields = { type FormFields = {
name: string name: string

View File

@ -9,19 +9,19 @@ import { ProfilePopup } from './ProfilePopup'
import Userpic from '../Author/Userpic' import Userpic from '../Author/Userpic'
import type { Author } from '../../graphql/types.gen' import type { Author } from '../../graphql/types.gen'
import { showModal, useWarningsStore } from '../../stores/ui' import { showModal, useWarningsStore } from '../../stores/ui'
import { useAuth } from '../../context/auth' import { ClientContainer } from '../_shared/ClientContainer'
import { useSession } from '../../context/session'
type HeaderAuthProps = { type HeaderAuthProps = {
setIsProfilePopupVisible: (value: boolean) => void setIsProfilePopupVisible: (value: boolean) => void
} }
export const HeaderAuth = (props: HeaderAuthProps) => { export const HeaderAuth = (props: HeaderAuthProps) => {
const [isMounted, setIsMounted] = createSignal(false)
const { page } = useRouter() const { page } = useRouter()
const [visibleWarnings, setVisibleWarnings] = createSignal(false) const [visibleWarnings, setVisibleWarnings] = createSignal(false)
const { warnings } = useWarningsStore() const { warnings } = useWarningsStore()
const { session, isAuthenticated } = useAuth() const { session, isAuthenticated } = useSession()
const toggleWarnings = () => setVisibleWarnings(!visibleWarnings()) const toggleWarnings = () => setVisibleWarnings(!visibleWarnings())
@ -36,12 +36,8 @@ export const HeaderAuth = (props: HeaderAuthProps) => {
toggleWarnings() toggleWarnings()
} }
onMount(() => {
setIsMounted(true)
})
return ( return (
<Show when={isMounted()}> <ClientContainer>
<Show when={!session.loading}> <Show when={!session.loading}>
<div class={styles.usernav}> <div class={styles.usernav}>
<div class={clsx(styles.userControl, styles.userControl, 'col')}> <div class={clsx(styles.userControl, styles.userControl, 'col')}>
@ -106,6 +102,6 @@ export const HeaderAuth = (props: HeaderAuthProps) => {
</div> </div>
</div> </div>
</Show> </Show>
</Show> </ClientContainer>
) )
} }

View File

@ -3,13 +3,13 @@ import type { Author } from '../../graphql/types.gen'
import { t } from '../../utils/intl' import { t } from '../../utils/intl'
import { hideModal } from '../../stores/ui' import { hideModal } from '../../stores/ui'
import { createMemo, For } from 'solid-js' import { createMemo, For } from 'solid-js'
import { useAuth } from '../../context/auth' import { useSession } from '../../context/session'
export const ProfileModal = () => { export const ProfileModal = () => {
const { const {
session, session,
actions: { signOut } actions: { signOut }
} = useAuth() } = useSession()
const quit = () => { const quit = () => {
signOut() signOut()

View File

@ -1,6 +1,7 @@
import { Popup, PopupProps } from './Popup' import { useSession } from '../../context/session'
import styles from './Popup.module.scss' import type { PopupProps } from '../_shared/Popup'
import { useAuth } from '../../context/auth' import { Popup } from '../_shared/Popup'
import styles from '../_shared/Popup.module.scss'
type ProfilePopupProps = Omit<PopupProps, 'children'> type ProfilePopupProps = Omit<PopupProps, 'children'>
@ -8,7 +9,7 @@ export const ProfilePopup = (props: ProfilePopupProps) => {
const { const {
session, session,
actions: { signOut } actions: { signOut }
} = useAuth() } = useSession()
return ( return (
<Popup {...props} horizontalAnchor="right"> <Popup {...props} horizontalAnchor="right">

View File

@ -29,7 +29,7 @@ import { TermsOfUsePage } from './Pages/about/TermsOfUsePage'
import { ThanksPage } from './Pages/about/ThanksPage' import { ThanksPage } from './Pages/about/ThanksPage'
import { CreatePage } from './Pages/CreatePage' import { CreatePage } from './Pages/CreatePage'
import { ConnectPage } from './Pages/ConnectPage' import { ConnectPage } from './Pages/ConnectPage'
import { AuthProvider } from '../context/auth' import { SessionProvider } from '../context/session'
// TODO: lazy load // TODO: lazy load
// const HomePage = lazy(() => import('./Pages/HomePage')) // const HomePage = lazy(() => import('./Pages/HomePage'))
@ -103,8 +103,8 @@ export const Root = (props: PageProps) => {
} }
return ( return (
<AuthProvider> <SessionProvider>
<Dynamic component={pageComponent()} {...props} /> <Dynamic component={pageComponent()} {...props} />
</AuthProvider> </SessionProvider>
) )
} }

View File

@ -8,7 +8,7 @@ import { locale } from '../../stores/ui'
import { follow, unfollow } from '../../stores/zine/common' import { follow, unfollow } from '../../stores/zine/common'
import { getLogger } from '../../utils/logger' import { getLogger } from '../../utils/logger'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import { useAuth } from '../../context/auth' import { useSession } from '../../context/session'
const log = getLogger('TopicCard') const log = getLogger('TopicCard')
@ -24,7 +24,7 @@ interface TopicProps {
} }
export const TopicCard = (props: TopicProps) => { export const TopicCard = (props: TopicProps) => {
const { session } = useAuth() const { session } = useSession()
const subscribed = createMemo(() => { const subscribed = createMemo(() => {
if (!session()?.user?.slug || !session()?.news?.topics) { if (!session()?.user?.slug || !session()?.news?.topics) {

View File

@ -5,14 +5,14 @@ import styles from './Full.module.scss'
import { follow, unfollow } from '../../stores/zine/common' import { follow, unfollow } from '../../stores/zine/common'
import { t } from '../../utils/intl' import { t } from '../../utils/intl'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import { useAuth } from '../../context/auth' import { useSession } from '../../context/session'
type Props = { type Props = {
topic: Topic topic: Topic
} }
export const FullTopic = (props: Props) => { export const FullTopic = (props: Props) => {
const { session } = useAuth() const { session } = useSession()
const subscribed = createMemo(() => session()?.news?.topics?.includes(props.topic?.slug)) const subscribed = createMemo(() => session()?.news?.topics?.includes(props.topic?.slug))
return ( return (

View File

@ -7,7 +7,7 @@ import { useAuthorsStore, setAuthorsSort } from '../../stores/zine/authors'
import { handleClientRouteLinkClick, useRouter } from '../../stores/router' import { handleClientRouteLinkClick, useRouter } from '../../stores/router'
import styles from '../../styles/AllTopics.module.scss' import styles from '../../styles/AllTopics.module.scss'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import { useAuth } from '../../context/auth' import { useSession } from '../../context/session'
type AllAuthorsPageSearchParams = { type AllAuthorsPageSearchParams = {
by: '' | 'name' | 'shouts' | 'rating' by: '' | 'name' | 'shouts' | 'rating'
@ -23,7 +23,7 @@ export const AllAuthorsView = (props: Props) => {
const { sortedAuthors } = useAuthorsStore({ authors: props.authors }) const { sortedAuthors } = useAuthorsStore({ authors: props.authors })
const [limit, setLimit] = createSignal(PAGE_SIZE) const [limit, setLimit] = createSignal(PAGE_SIZE)
const { session } = useAuth() const { session } = useSession()
createEffect(() => { createEffect(() => {
setAuthorsSort(searchParams().by || 'shouts') setAuthorsSort(searchParams().by || 'shouts')

View File

@ -7,7 +7,7 @@ import { handleClientRouteLinkClick, useRouter } from '../../stores/router'
import { TopicCard } from '../Topic/Card' import { TopicCard } from '../Topic/Card'
import styles from '../../styles/AllTopics.module.scss' import styles from '../../styles/AllTopics.module.scss'
import { clsx } from 'clsx' import { clsx } from 'clsx'
import { useAuth } from '../../context/auth' import { useSession } from '../../context/session'
type AllTopicsPageSearchParams = { type AllTopicsPageSearchParams = {
by: 'shouts' | 'authors' | 'title' | '' by: 'shouts' | 'authors' | 'title' | ''
@ -28,7 +28,7 @@ export const AllTopicsView = (props: AllTopicsViewProps) => {
sortBy: searchParams().by || 'shouts' sortBy: searchParams().by || 'shouts'
}) })
const { session } = useAuth() const { session } = useSession()
createEffect(() => { createEffect(() => {
setTopicsSort(searchParams().by || 'shouts') setTopicsSort(searchParams().by || 'shouts')

View File

@ -1,17 +1,11 @@
import { Show, onMount, createSignal } from 'solid-js'
import { Editor } from '../EditorNew/Editor' import { Editor } from '../EditorNew/Editor'
import { ClientContainer } from '../_shared/ClientContainer'
export const CreateView = () => { export const CreateView = () => {
// don't render anything on server
// usage of isServer causing hydration errors
const [isMounted, setIsMounted] = createSignal(false)
onMount(() => setIsMounted(true))
return ( return (
<Show when={isMounted()}> <ClientContainer>
<Editor /> <Editor />
</Show> </ClientContainer>
) )
} }

View File

@ -14,7 +14,7 @@ import { useReactionsStore } from '../../stores/zine/reactions'
import { useAuthorsStore } from '../../stores/zine/authors' import { useAuthorsStore } from '../../stores/zine/authors'
import { useTopicsStore } from '../../stores/zine/topics' import { useTopicsStore } from '../../stores/zine/topics'
import { useTopAuthorsStore } from '../../stores/zine/topAuthors' import { useTopAuthorsStore } from '../../stores/zine/topAuthors'
import { useAuth } from '../../context/auth' import { useSession } from '../../context/session'
// const AUTHORSHIP_REACTIONS = [ // const AUTHORSHIP_REACTIONS = [
// ReactionKind.Accept, // ReactionKind.Accept,
@ -32,7 +32,7 @@ export const FeedView = () => {
const { sortedAuthors } = useAuthorsStore() const { sortedAuthors } = useAuthorsStore()
const { topTopics } = useTopicsStore() const { topTopics } = useTopicsStore()
const { topAuthors } = useTopAuthorsStore() const { topAuthors } = useTopAuthorsStore()
const { session } = useAuth() const { session } = useSession()
const topReactions = createMemo(() => sortBy(reactions(), byCreated)) const topReactions = createMemo(() => sortBy(reactions(), byCreated))

View File

@ -0,0 +1,12 @@
import type { JSX } from 'solid-js'
import { createSignal, onMount, Show } from 'solid-js'
// show children only on client side
// usage of isServer causing hydration errors
export const ClientContainer = (props: { children: JSX.Element }) => {
const [isMounted, setIsMounted] = createSignal(false)
onMount(() => setIsMounted(true))
return <Show when={isMounted()}>{props.children}</Show>
}

View File

@ -4,7 +4,7 @@ import type { AuthResult } from '../graphql/types.gen'
import { apiClient } from '../utils/apiClient' import { apiClient } from '../utils/apiClient'
import { resetToken, setToken } from '../graphql/privateGraphQLClient' import { resetToken, setToken } from '../graphql/privateGraphQLClient'
type AuthContextType = { type SessionContextType = {
session: InitializedResource<AuthResult> session: InitializedResource<AuthResult>
isAuthenticated: Accessor<boolean> isAuthenticated: Accessor<boolean>
actions: { actions: {
@ -15,7 +15,7 @@ type AuthContextType = {
} }
} }
const AuthContext = createContext<AuthContextType>() const SessionContext = createContext<SessionContextType>()
const refreshSession = async (): Promise<AuthResult> => { const refreshSession = async (): Promise<AuthResult> => {
try { try {
@ -32,31 +32,11 @@ const refreshSession = async (): Promise<AuthResult> => {
} }
} }
export const register = async ({ export function useSession() {
name, return useContext(SessionContext)
email,
password
}: {
name: string
email: string
password: string
}) => {
await apiClient.authRegister({
name,
email,
password
})
} }
export const signSendLink = async ({ email, lang }: { email: string; lang: string }) => { export const SessionProvider = (props: { children: JSX.Element }) => {
return await apiClient.authSendLink({ email, lang })
}
export function useAuth() {
return useContext(AuthContext)
}
export const AuthProvider = (props: { children: JSX.Element }) => {
const [session, { refetch: refetchRefreshSession, mutate }] = createResource<AuthResult>(refreshSession, { const [session, { refetch: refetchRefreshSession, mutate }] = createResource<AuthResult>(refreshSession, {
ssrLoadFrom: 'initial', ssrLoadFrom: 'initial',
initialValue: null initialValue: null
@ -91,11 +71,11 @@ export const AuthProvider = (props: { children: JSX.Element }) => {
confirmEmail confirmEmail
} }
const value: AuthContextType = { session, isAuthenticated, actions } const value: SessionContextType = { session, isAuthenticated, actions }
onMount(() => { onMount(() => {
refetchRefreshSession() refetchRefreshSession()
}) })
return <AuthContext.Provider value={value}>{props.children}</AuthContext.Provider> return <SessionContext.Provider value={value}>{props.children}</SessionContext.Provider>
} }

21
src/stores/auth.ts Normal file
View File

@ -0,0 +1,21 @@
import { apiClient } from '../utils/apiClient'
export const register = async ({
name,
email,
password
}: {
name: string
email: string
password: string
}) => {
await apiClient.authRegister({
name,
email,
password
})
}
export const signSendLink = async ({ email, lang }: { email: string; lang: string }) => {
return await apiClient.authSendLink({ email, lang })
}