my feed with auth guard (#245)

* my feed with auth guard

* header links css fix

* don't create history record when reseting lng param

---------

Co-authored-by: Igor Lobanov <igor.lobanov@onetwotrip.com>
This commit is contained in:
Ilya Y 2023-09-29 15:48:58 +03:00 committed by GitHub
parent 18ec665bb2
commit aadc9677a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 328 additions and 240 deletions

25
package-lock.json generated
View File

@ -64,6 +64,7 @@
"@tiptap/extension-text": "2.0.3", "@tiptap/extension-text": "2.0.3",
"@tiptap/extension-underline": "2.0.3", "@tiptap/extension-underline": "2.0.3",
"@tiptap/extension-youtube": "2.0.3", "@tiptap/extension-youtube": "2.0.3",
"@types/js-cookie": "3.0.4",
"@types/node": "20.1.1", "@types/node": "20.1.1",
"@typescript-eslint/eslint-plugin": "6.7.3", "@typescript-eslint/eslint-plugin": "6.7.3",
"@typescript-eslint/parser": "6.7.3", "@typescript-eslint/parser": "6.7.3",
@ -5155,6 +5156,12 @@
"@types/istanbul-lib-report": "*" "@types/istanbul-lib-report": "*"
} }
}, },
"node_modules/@types/js-cookie": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.4.tgz",
"integrity": "sha512-vMMnFF+H5KYqdd/myCzq6wLDlPpteJK+jGFgBus3Da7lw+YsDmx2C8feGTzY2M3Fo823yON+HC2CL240j4OV+w==",
"dev": true
},
"node_modules/@types/js-yaml": { "node_modules/@types/js-yaml": {
"version": "4.0.6", "version": "4.0.6",
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.6.tgz", "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.6.tgz",
@ -5249,9 +5256,9 @@
} }
}, },
"node_modules/@types/yargs": { "node_modules/@types/yargs": {
"version": "17.0.25", "version": "17.0.26",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.25.tgz", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.26.tgz",
"integrity": "sha512-gy7iPgwnzNvxgAEi2bXOHWCVOG6f7xsprVJH4MjlAWeBmJ7vh/Y1kwMtUrs64ztf24zVIRCpr3n/z6gm9QIkgg==", "integrity": "sha512-Y3vDy2X6zw/ZCumcwLpdhM5L7jmyGpmBCTYMHDLqT2IKVMYRRLdv6ZakA+wxhra6Z/3bwhNbNl9bDGXaFU+6rw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/yargs-parser": "*" "@types/yargs-parser": "*"
@ -21885,6 +21892,12 @@
"@types/istanbul-lib-report": "*" "@types/istanbul-lib-report": "*"
} }
}, },
"@types/js-cookie": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.4.tgz",
"integrity": "sha512-vMMnFF+H5KYqdd/myCzq6wLDlPpteJK+jGFgBus3Da7lw+YsDmx2C8feGTzY2M3Fo823yON+HC2CL240j4OV+w==",
"dev": true
},
"@types/js-yaml": { "@types/js-yaml": {
"version": "4.0.6", "version": "4.0.6",
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.6.tgz", "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.6.tgz",
@ -21979,9 +21992,9 @@
} }
}, },
"@types/yargs": { "@types/yargs": {
"version": "17.0.25", "version": "17.0.26",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.25.tgz", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.26.tgz",
"integrity": "sha512-gy7iPgwnzNvxgAEi2bXOHWCVOG6f7xsprVJH4MjlAWeBmJ7vh/Y1kwMtUrs64ztf24zVIRCpr3n/z6gm9QIkgg==", "integrity": "sha512-Y3vDy2X6zw/ZCumcwLpdhM5L7jmyGpmBCTYMHDLqT2IKVMYRRLdv6ZakA+wxhra6Z/3bwhNbNl9bDGXaFU+6rw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/yargs-parser": "*" "@types/yargs-parser": "*"

View File

@ -84,6 +84,7 @@
"@tiptap/extension-text": "2.0.3", "@tiptap/extension-text": "2.0.3",
"@tiptap/extension-underline": "2.0.3", "@tiptap/extension-underline": "2.0.3",
"@tiptap/extension-youtube": "2.0.3", "@tiptap/extension-youtube": "2.0.3",
"@types/js-cookie": "3.0.4",
"@types/node": "20.1.1", "@types/node": "20.1.1",
"@typescript-eslint/eslint-plugin": "6.7.3", "@typescript-eslint/eslint-plugin": "6.7.3",
"@typescript-eslint/parser": "6.7.3", "@typescript-eslint/parser": "6.7.3",

View File

@ -1,7 +1,7 @@
// FIXME: breaks on vercel, research // FIXME: breaks on vercel, research
// import 'solid-devtools' // import 'solid-devtools'
import { MODALS, showModal } from '../stores/ui' import { hideModal, MODALS, showModal } from '../stores/ui'
import { Component, createEffect, createMemo } from 'solid-js' import { Component, createEffect, createMemo } from 'solid-js'
import { ROUTES, useRouter } from '../stores/router' import { ROUTES, useRouter } from '../stores/router'
import { Dynamic } from 'solid-js/web' import { Dynamic } from 'solid-js/web'
@ -89,6 +89,10 @@ export const App = (props: PageProps) => {
const { page, searchParams } = useRouter<RootSearchParams>() const { page, searchParams } = useRouter<RootSearchParams>()
createEffect(() => { createEffect(() => {
if (!searchParams().modal) {
hideModal()
}
const modal = MODALS[searchParams().modal] const modal = MODALS[searchParams().modal]
if (modal) { if (modal) {
showModal(modal) showModal(modal)

View File

@ -97,7 +97,9 @@ export const FullArticle = (props: Props) => {
createEffect(() => { createEffect(() => {
if (searchParams()?.scrollTo === 'comments' && commentsRef.current) { if (searchParams()?.scrollTo === 'comments' && commentsRef.current) {
scrollToComments() scrollToComments()
changeSearchParam('scrollTo', null) changeSearchParam({
scrollTo: null
})
} }
}) })

View File

@ -1,6 +1,9 @@
import { createEffect, JSX, Show } from 'solid-js' import { createEffect, JSX, Show } from 'solid-js'
import { useSession } from '../../context/session' import { useSession } from '../../context/session'
import { hideModal, showModal } from '../../stores/ui' import { hideModal, showModal } from '../../stores/ui'
import { useRouter } from '../../stores/router'
import { RootSearchParams } from '../../pages/types'
import { AuthModalSearchParams } from '../Nav/AuthModal/types'
type Props = { type Props = {
children: JSX.Element children: JSX.Element
@ -9,6 +12,7 @@ type Props = {
export const AuthGuard = (props: Props) => { export const AuthGuard = (props: Props) => {
const { isAuthenticated, isSessionLoaded } = useSession() const { isAuthenticated, isSessionLoaded } = useSession()
const { changeSearchParam } = useRouter<RootSearchParams & AuthModalSearchParams>()
createEffect(() => { createEffect(() => {
if (props.disabled) { if (props.disabled) {
@ -18,7 +22,13 @@ export const AuthGuard = (props: Props) => {
if (isAuthenticated()) { if (isAuthenticated()) {
hideModal() hideModal()
} else { } else {
showModal('auth', 'authguard') changeSearchParam(
{
source: 'authguard',
modal: 'auth'
},
true
)
} }
} }
}) })

View File

@ -93,7 +93,9 @@ export const AuthorCard = (props: Props) => {
const initChat = () => { const initChat = () => {
requireAuthentication(() => { requireAuthentication(() => {
openPage(router, `inbox`) openPage(router, `inbox`)
changeSearchParam('initChat', `${props.author.id}`) changeSearchParam({
initChat: props.author.id.toString()
})
}, 'discussions') }, 'discussions')
} }

View File

@ -28,7 +28,9 @@ export default () => {
class="button" class="button"
onClick={() => { onClick={() => {
showModal('auth') showModal('auth')
changeSearchParam('mode', 'register') changeSearchParam({
mode: 'register'
})
}} }}
> >
{t('Join the community')} {t('Join the community')}

View File

@ -84,7 +84,9 @@ export const ArticleCard = (props: ArticleCardProps) => {
const scrollToComments = (event) => { const scrollToComments = (event) => {
event.preventDefault() event.preventDefault()
openPage(router, 'article', { slug: props.article.slug }) openPage(router, 'article', { slug: props.article.slug })
changeSearchParam('scrollTo', 'comments') changeSearchParam({
scrollTo: 'comments'
})
} }
const [isActionPopupActive, setIsActionPopupActive] = createSignal(false) const [isActionPopupActive, setIsActionPopupActive] = createSignal(false)

View File

@ -115,7 +115,9 @@ export const ForgotPasswordForm = () => {
href="#" href="#"
onClick={(event) => { onClick={(event) => {
event.preventDefault() event.preventDefault()
changeSearchParam('mode', 'register') changeSearchParam({
mode: 'register'
})
}} }}
> >
{t('register')} {t('register')}
@ -132,7 +134,14 @@ export const ForgotPasswordForm = () => {
</button> </button>
</div> </div>
<div class={styles.authControl}> <div class={styles.authControl}>
<span class={styles.authLink} onClick={() => changeSearchParam('mode', 'login')}> <span
class={styles.authLink}
onClick={() =>
changeSearchParam({
mode: 'login'
})
}
>
{t('I know the password')} {t('I know the password')}
</span> </span>
</div> </div>

View File

@ -195,11 +195,12 @@ export const LoginForm = () => {
</div> </div>
<div class={styles.authActions}> <div class={styles.authActions}>
<span <span
class={'link'} class="link"
onClick={(ev) => { onClick={() =>
ev.preventDefault() changeSearchParam({
changeSearchParam('mode', 'forgot-password') mode: 'forgot-password'
}} })
}
> >
{t('Forgot password?')} {t('Forgot password?')}
</span> </span>
@ -210,7 +211,14 @@ export const LoginForm = () => {
<SocialProviders /> <SocialProviders />
<div class={styles.authControl}> <div class={styles.authControl}>
<span class={styles.authLink} onClick={() => changeSearchParam('mode', 'register')}> <span
class={styles.authLink}
onClick={() =>
changeSearchParam({
mode: 'register'
})
}
>
{t('I have no account yet')} {t('I have no account yet')}
</span> </span>
</div> </div>

View File

@ -198,7 +198,9 @@ export const RegisterForm = () => {
href="#" href="#"
onClick={(event) => { onClick={(event) => {
event.preventDefault() event.preventDefault()
changeSearchParam('mode', 'login') changeSearchParam({
mode: 'login'
})
}} }}
> >
{t('enter')} {t('enter')}
@ -246,7 +248,14 @@ export const RegisterForm = () => {
<SocialProviders /> <SocialProviders />
<div class={styles.authControl}> <div class={styles.authControl}>
<span class={styles.authLink} onClick={() => changeSearchParam('mode', 'login')}> <span
class={styles.authLink}
onClick={() =>
changeSearchParam({
mode: 'login'
})
}
>
{t('I have an account')} {t('I have an account')}
</span> </span>
</div> </div>

View File

@ -251,7 +251,7 @@
.mainNavigationItemActive { .mainNavigationItemActive {
background: var(--link-hover-background); background: var(--link-hover-background);
color: var(--link-hover-color); color: var(--link-hover-color) !important;
} }
.headerWithTitle.headerScrolledBottom { .headerWithTitle.headerScrolledBottom {

View File

@ -40,15 +40,9 @@ export const ProfilePopup = (props: ProfilePopupProps) => {
<a href={getPagePath(router, 'profileSettings')}>{t('Settings')}</a> <a href={getPagePath(router, 'profileSettings')}>{t('Settings')}</a>
</li> </li>
<li class={styles.topBorderItem}> <li class={styles.topBorderItem}>
<a <span class="link" onClick={() => signOut()}>
href="#"
onClick={(event) => {
event.preventDefault()
signOut()
}}
>
{t('Logout')} {t('Logout')}
</a> </span>
</li> </li>
</ul> </ul>
</Popup> </Popup>

View File

@ -38,7 +38,9 @@ export const AllAuthorsView = (props: AllAuthorsViewProps) => {
onMount(() => { onMount(() => {
if (!searchParams().by) { if (!searchParams().by) {
changeSearchParam('by', 'shouts') changeSearchParam({
by: 'shouts'
})
} }
}) })
@ -47,7 +49,8 @@ export const AllAuthorsView = (props: AllAuthorsViewProps) => {
}) })
const byLetter = createMemo<{ [letter: string]: Author[] }>(() => { const byLetter = createMemo<{ [letter: string]: Author[] }>(() => {
return sortedAuthors().reduce((acc, author) => { return sortedAuthors().reduce(
(acc, author) => {
let letter = author.name.trim().split(' ').pop().at(0).toUpperCase() let letter = author.name.trim().split(' ').pop().at(0).toUpperCase()
if (/[^ËА-яё]/.test(letter) && lang() === 'ru') letter = '@' if (/[^ËА-яё]/.test(letter) && lang() === 'ru') letter = '@'
@ -56,7 +59,9 @@ export const AllAuthorsView = (props: AllAuthorsViewProps) => {
acc[letter].push(author) acc[letter].push(author)
return acc return acc
}, {} as { [letter: string]: Author[] }) },
{} as { [letter: string]: Author[] }
)
}) })
const sortedKeys = createMemo<string[]>(() => { const sortedKeys = createMemo<string[]>(() => {

View File

@ -38,7 +38,9 @@ export const AllTopicsView = (props: AllTopicsViewProps) => {
onMount(() => { onMount(() => {
if (!searchParams().by) { if (!searchParams().by) {
changeSearchParam('by', 'shouts') changeSearchParam({
by: 'shouts'
})
} }
}) })
@ -47,13 +49,16 @@ export const AllTopicsView = (props: AllTopicsViewProps) => {
}) })
const byLetter = createMemo<{ [letter: string]: Topic[] }>(() => { const byLetter = createMemo<{ [letter: string]: Topic[] }>(() => {
return sortedTopics().reduce((acc, topic) => { return sortedTopics().reduce(
(acc, topic) => {
let letter = topic.title[0].toUpperCase() let letter = topic.title[0].toUpperCase()
if (/[^ËА-яё]/.test(letter) && lang() === 'ru') letter = '#' if (/[^ËА-яё]/.test(letter) && lang() === 'ru') letter = '#'
if (!acc[letter]) acc[letter] = [] if (!acc[letter]) acc[letter] = []
acc[letter].push(topic) acc[letter].push(topic)
return acc return acc
}, {} as { [letter: string]: Topic[] }) },
{} as { [letter: string]: Topic[] }
)
}) })
const sortedKeys = createMemo<string[]>(() => { const sortedKeys = createMemo<string[]>(() => {

View File

@ -3,13 +3,13 @@ import { Icon } from '../_shared/Icon'
import { ArticleCard } from '../Feed/ArticleCard' import { ArticleCard } from '../Feed/ArticleCard'
import { AuthorCard } from '../Author/AuthorCard' import { AuthorCard } from '../Author/AuthorCard'
import { Sidebar } from '../Feed/Sidebar' import { Sidebar } from '../Feed/Sidebar'
import { loadShouts, loadMyFeed, useArticlesStore, resetSortedArticles } from '../../stores/zine/articles' import { useArticlesStore, resetSortedArticles } from '../../stores/zine/articles'
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 { clsx } from 'clsx' import { clsx } from 'clsx'
import { useReactions } from '../../context/reactions' import { useReactions } from '../../context/reactions'
import type { Author, LoadShoutsOptions, Reaction } from '../../graphql/types.gen' import type { Author, LoadShoutsOptions, Reaction, Shout } from '../../graphql/types.gen'
import { getPagePath } from '@nanostores/router' import { getPagePath } from '@nanostores/router'
import { router, useRouter } from '../../stores/router' import { router, useRouter } from '../../stores/router'
import { useLocalize } from '../../context/localize' import { useLocalize } from '../../context/localize'
@ -18,8 +18,6 @@ import stylesTopic from '../Feed/CardTopic.module.scss'
import stylesBeside from '../../components/Feed/Beside.module.scss' import stylesBeside from '../../components/Feed/Beside.module.scss'
import { CommentDate } from '../Article/CommentDate' import { CommentDate } from '../Article/CommentDate'
import { Loading } from '../_shared/Loading' import { Loading } from '../_shared/Loading'
import { AuthGuard } from '../AuthGuard'
import { useSession } from '../../context/session'
export const FEED_PAGE_SIZE = 20 export const FEED_PAGE_SIZE = 20
@ -39,17 +37,13 @@ const getOrderBy = (by: FeedSearchParams['by']) => {
return '' return ''
} }
const routesWithAuthGuard = new Set([ type Props = {
'feedMy', loadShouts: (options: LoadShoutsOptions) => Promise<{ hasMore: boolean; newShouts: Shout[] }>
'feedNotifications', }
'feedBookmarks',
'feedCollaborations', export const FeedView = (props: Props) => {
'feedDiscussions'
])
export const FeedView = () => {
const { t } = useLocalize() const { t } = useLocalize()
const { page, searchParams } = useRouter<FeedSearchParams>() const { page, searchParams } = useRouter<FeedSearchParams>()
const { isAuthenticated } = useSession()
const [isLoading, setIsLoading] = createSignal(false) const [isLoading, setIsLoading] = createSignal(false)
// state // state
@ -91,15 +85,7 @@ export const FeedView = () => {
options.order_by = orderBy options.order_by = orderBy
} }
if (isAuthenticated()) { return props.loadShouts(options)
return loadMyFeed(options)
}
// default feed
return loadShouts({
...options,
filters: { visibility: 'community' }
})
} }
const loadMore = async () => { const loadMore = async () => {
@ -124,8 +110,6 @@ export const FeedView = () => {
}) })
return ( return (
<div>
<AuthGuard disabled={!routesWithAuthGuard.has(page().route)}>
<div class="wide-container feed"> <div class="wide-container feed">
<div class="row"> <div class="row">
<div class={clsx('col-md-5 col-xl-4', styles.feedNavigation)}> <div class={clsx('col-md-5 col-xl-4', styles.feedNavigation)}>
@ -136,8 +120,7 @@ export const FeedView = () => {
<ul class={clsx(styles.feedFilter, 'view-switcher')}> <ul class={clsx(styles.feedFilter, 'view-switcher')}>
<li <li
class={clsx({ class={clsx({
'view-switcher__item--selected': 'view-switcher__item--selected': searchParams().by === 'publish_date' || !searchParams().by
searchParams().by === 'publish_date' || !searchParams().by
})} })}
> >
<a href={getPagePath(router, page().route)}>{t('Recent')}</a> <a href={getPagePath(router, page().route)}>{t('Recent')}</a>
@ -275,7 +258,5 @@ export const FeedView = () => {
</aside> </aside>
</div> </div>
</div> </div>
</AuthGuard>
</div>
) )
} }

View File

@ -64,7 +64,9 @@ export const InboxView = () => {
const handleOpenChat = async (chat: Chat) => { const handleOpenChat = async (chat: Chat) => {
setCurrentDialog(chat) setCurrentDialog(chat)
changeSearchParam('chat', `${chat.id}`) changeSearchParam({
chat: chat.id
})
try { try {
await getMessages(chat.id) await getMessages(chat.id)
} catch (error) { } catch (error) {
@ -121,8 +123,10 @@ export const InboxView = () => {
try { try {
const newChat = await createChat([Number(searchParams().initChat)], '') const newChat = await createChat([Number(searchParams().initChat)], '')
await loadChats() await loadChats()
changeSearchParam('initChat', null) changeSearchParam({
changeSearchParam('chat', newChat.chat.id) initChat: null,
chat: newChat.chat.id
})
const chatToOpen = chats().find((chat) => chat.id === newChat.chat.id) const chatToOpen = chats().find((chat) => chat.id === newChat.chat.id)
await handleOpenChat(chatToOpen) await handleOpenChat(chatToOpen)
} catch (error) { } catch (error) {

View File

@ -88,7 +88,14 @@ export const TopicView = (props: TopicProps) => {
'view-switcher__item--selected': searchParams().by === 'recent' || !searchParams().by 'view-switcher__item--selected': searchParams().by === 'recent' || !searchParams().by
}} }}
> >
<button type="button" onClick={() => changeSearchParam('by', 'recent')}> <button
type="button"
onClick={() =>
changeSearchParam({
by: 'recent'
})
}
>
{t('Recent')} {t('Recent')}
</button> </button>
</li> </li>

View File

@ -33,7 +33,7 @@ export const LocalizeProvider = (props: { children: JSX.Element }) => {
changeLanguage(lng) changeLanguage(lng)
setLang(lng) setLang(lng)
Cookie.set('lng', lng) Cookie.set('lng', lng)
changeSearchParam('lng', null) changeSearchParam({ lng: null }, true)
}) })
const value: LocalizeContextType = { t, lang, setLang } const value: LocalizeContextType = { t, lang, setLang }

View File

@ -74,8 +74,8 @@ export const SessionProvider = (props: { children: JSX.Element }) => {
const signIn = async ({ email, password }: { email: string; password: string }) => { const signIn = async ({ email, password }: { email: string; password: string }) => {
const authResult = await apiClient.authLogin({ email, password }) const authResult = await apiClient.authLogin({ email, password })
mutate(authResult)
setToken(authResult.token) setToken(authResult.token)
mutate(authResult)
console.debug('signed in') console.debug('signed in')
} }

View File

@ -1,16 +1,41 @@
import { PageLayout } from '../components/_shared/PageLayout' import { PageLayout } from '../components/_shared/PageLayout'
import { FeedView } from '../components/Views/Feed' import { FeedView } from '../components/Views/Feed'
import { onCleanup } from 'solid-js' import { Match, onCleanup, Switch } from 'solid-js'
import { resetSortedArticles } from '../stores/zine/articles' import { loadMyFeed, loadShouts, resetSortedArticles } from '../stores/zine/articles'
import { ReactionsProvider } from '../context/reactions' import { ReactionsProvider } from '../context/reactions'
import { useRouter } from '../stores/router'
import { AuthGuard } from '../components/AuthGuard'
import { LoadShoutsOptions } from '../graphql/types.gen'
export const FeedPage = () => { export const FeedPage = () => {
onCleanup(() => resetSortedArticles()) onCleanup(() => resetSortedArticles())
const { page } = useRouter()
const handleFeedLoadShouts = (options: LoadShoutsOptions) => {
return loadShouts({
...options,
filters: { visibility: 'community' }
})
}
const handleMyFeedLoadShouts = (options: LoadShoutsOptions) => {
return loadMyFeed(options)
}
return ( return (
<PageLayout> <PageLayout>
<ReactionsProvider> <ReactionsProvider>
<FeedView /> <Switch fallback={<FeedView loadShouts={handleFeedLoadShouts} />}>
<Match when={page().route === 'feed'}>
<FeedView loadShouts={handleFeedLoadShouts} />
</Match>
<Match when={page().route === 'feedMy'}>
<AuthGuard>
<FeedView loadShouts={handleMyFeedLoadShouts} />
</AuthGuard>
</Match>
</Switch>
</ReactionsProvider> </ReactionsProvider>
</PageLayout> </PageLayout>
) )

View File

@ -137,17 +137,16 @@ export const useRouter = <TSearchParams extends Record<string, string> = Record<
const page = useStore(routerStore) const page = useStore(routerStore)
const searchParams = useStore(searchParamsStore) as unknown as Accessor<TSearchParams> const searchParams = useStore(searchParamsStore) as unknown as Accessor<TSearchParams>
const changeSearchParam = <TKey extends keyof TSearchParams>( const changeSearchParam = (newValues: Partial<TSearchParams>, replace = false) => {
key: TKey,
value: TSearchParams[TKey],
replace = false
) => {
const newSearchParams = { ...searchParamsStore.get() } const newSearchParams = { ...searchParamsStore.get() }
if (value === null) {
Object.keys(newValues).forEach((key) => {
if (newValues[key] === null) {
delete newSearchParams[key.toString()] delete newSearchParams[key.toString()]
} else { } else {
newSearchParams[key.toString()] = value newSearchParams[key.toString()] = newValues[key]
} }
})
searchParamsStore.open(newSearchParams, replace) searchParamsStore.open(newSearchParams, replace)
} }

View File

@ -58,7 +58,9 @@ const { searchParams, changeSearchParam } = useRouter<
export const showModal = (modalType: ModalType, modalSource?: AuthModalSource) => { export const showModal = (modalType: ModalType, modalSource?: AuthModalSource) => {
if (modalSource) { if (modalSource) {
changeSearchParam('source', modalSource) changeSearchParam({
source: modalSource
})
} }
setModal(modalType) setModal(modalType)
@ -66,15 +68,19 @@ export const showModal = (modalType: ModalType, modalSource?: AuthModalSource) =
// TODO: find a better solution // TODO: find a better solution
export const hideModal = () => { export const hideModal = () => {
if (searchParams().modal === 'auth') { const newSearchParams: Partial<AuthModalSearchParams & ConfirmEmailSearchParams & RootSearchParams> = {
if (searchParams().mode === 'confirm-email') { modal: null,
changeSearchParam('token', null, true) source: null
}
changeSearchParam('mode', null, true)
} }
changeSearchParam('modal', null, true) if (searchParams().modal === 'auth') {
changeSearchParam('source', null) if (searchParams().mode === 'confirm-email') {
newSearchParams.token = null
}
newSearchParams.mode = null
}
changeSearchParam(newSearchParams, true)
setModal(null) setModal(null)
} }