Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

Commit

Permalink
Create use-cookies composable
Browse files Browse the repository at this point in the history
Move all the UI cookie updates to the UI store
  • Loading branch information
obulat committed Nov 4, 2022
1 parent 307d51c commit 7037c00
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 92 deletions.
31 changes: 31 additions & 0 deletions src/composables/use-cookies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { NuxtAppOptions } from '@nuxt/types'

type CookieValue = string | Record<string, boolean | string | undefined>

export const useCookies = <T extends CookieValue>(
app: NuxtAppOptions,
name: string
) => {
/**
* Sets the cookie with the `value`, using the useCookie's `name`.
*
* @param value - if not a string, this value is converted to string using
* `JSON.stringify`.
*/
const setCookie = (value: T) => {
const cookieValue =
typeof value === 'string' ? value : JSON.stringify(value)

app.$cookies.set(name, cookieValue, { sameSite: true })
}

const getCookie = (): string | undefined => {
return app.$cookies.get(name)
}

return {
setCookie,
getCookie,
}
}
export default useCookies
5 changes: 3 additions & 2 deletions src/middleware/middleware.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { sendWindowMessage } from '~/utils/send-message'

import { useNavigationStore } from '~/stores/navigation'
import { useProviderStore } from '~/stores/provider'
import { useFeatureFlagStore } from '~/stores/feature-flag'

import { useUiStore } from '~/stores/ui'

import type { Middleware } from '@nuxt/types'
Expand Down Expand Up @@ -52,6 +52,7 @@ const middleware: Middleware = async ({ app, query, route, $pinia }) => {
/* UI store */

const uiStore = useUiStore($pinia)
uiStore.initFromCookies(app.$cookies.get('ui') ?? {})
const isMobileUa = app.$ua ? app.$ua.isMobile : false
uiStore.initFromCookies(app.$cookies.get('ui') ?? {}, isMobileUa)
}
export default middleware
110 changes: 60 additions & 50 deletions src/stores/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,22 @@ export const NOT_SHOWN = 'not_shown'
const snackbarStates = [NOT_SHOWN, VISIBLE, DISMISSED] as const
export type SnackbarState = typeof snackbarStates[number]

interface UiStateCookie {
isDesktopLayout?: boolean | undefined
isMobileUa?: boolean | undefined
isFilterDismissed?: boolean | undefined
export interface UiStateCookie {
isDesktopLayout?: boolean
isMobileUa?: boolean
isFilterDismissed?: boolean
}

export interface ScreenParameters {
isDesktopLayout: boolean
isMobileUa: boolean
}
export interface UiState {
/**
* whether to show the instructions snackbar.
*/
instructionsSnackbarState: SnackbarState
/**
* whether the filters are shown (sidebar on desktop or modal on mobile).
* This is the inner value, components should use the un-prefixed getter.
*/
isFilterVisible: boolean
innerFilterVisible: boolean
/**
* (only for desktop layout) whether the filters were dismissed. If true,
* the filters should be closed on SSR for the first load on desktop layout.
Expand All @@ -44,10 +41,12 @@ export interface UiState {
isMobileUa: boolean
}

type UiCookieSetter = (value: UiStateCookie) => void

export const useUiStore = defineStore('ui', {
state: (): UiState => ({
instructionsSnackbarState: NOT_SHOWN,
isFilterVisible: false,
innerFilterVisible: false,
isFilterDismissed: false,
isDesktopLayout: false,
isMobileUa: true,
Expand All @@ -57,6 +56,33 @@ export const useUiStore = defineStore('ui', {
areInstructionsVisible(state): boolean {
return state.instructionsSnackbarState === VISIBLE
},
uiCookie(state): UiStateCookie {
return {
isDesktopLayout: state.isDesktopLayout,
isMobileUa: state.isMobileUa,
isFilterDismissed: state.isFilterDismissed,
}
},
/**
* On desktop, we only hide the filters sidebar if it was
* specifically dismissed on the desktop layout.
*
* The filters state could diverge if the layout changed from desktop to mobile:
* closing the filters on mobile sets `isFilterVisible` to false, but does not
* save the `dismissed` because it only affects the desktop layout.
*
* Here, we first correct the state if it diverged, and then return visibility state.
*/
isFilterVisible(state): boolean {
if (
state.isDesktopLayout &&
!state.isFilterDismissed &&
!state.innerFilterVisible
) {
state.innerFilterVisible = true
}
return state.innerFilterVisible
},
},

actions: {
Expand All @@ -76,77 +102,61 @@ export const useUiStore = defineStore('ui', {
* if the breakpoints change.
*
* @param cookies - mapping of UI state parameters and their states.
* @param isMobile - whether the request is with a mobile user agent.
*/
initFromCookies(cookies: UiStateCookie) {
initFromCookies(cookies: UiStateCookie, isMobile: boolean | null) {
this.isDesktopLayout = cookies.isDesktopLayout ?? false
this.isMobileUa = cookies.isMobileUa ?? false
this.isMobileUa = Boolean((isMobile ?? false) || cookies.isMobileUa)
this.isFilterDismissed = cookies.isFilterDismissed ?? false
this.isFilterVisible = this.isDesktopLayout
this.innerFilterVisible = this.isDesktopLayout
? !this.isFilterDismissed
: false
},
/**
* Sets the breakpoint and ua state. We have to set the cookie value inside
* the `setup` function because `app.$cookies` is not available outside it.
* This is why we return the object that needs to be saved in the cookies.
* Sets the breakpoint and user agent state, and saves it into app cookies.
*
* @param isDesktopLayout - whether the layout is desktop (`lg` with the `new_header`
* and `md` with the `old_header`).
* @param isMobileUa - true if the request's user agent is `mobile`, `undefined` otherwise.
* @param isMobileUa - whether the request's user agent is `mobile` or not.
* @param setCookieFn - sets the app cookie.
*/
updateBreakpoint(
isDesktopLayout: boolean,
isMobileUa: boolean | undefined
): ScreenParameters | null {
isMobileUa: boolean,
setCookieFn: UiCookieSetter
) {
if (
this.isDesktopLayout === isDesktopLayout &&
typeof isMobileUa === 'undefined'
this.isMobileUa === isMobileUa
) {
return null
return
}

this.isDesktopLayout = isDesktopLayout
this.isMobileUa = isMobileUa

// Only update `isMobileUa` if the value is provided
if (typeof isMobileUa !== 'undefined') {
this.isMobileUa = isMobileUa
}

return {
isDesktopLayout: this.isDesktopLayout,
isMobileUa: this.isMobileUa,
}
setCookieFn(this.uiCookie)
},
/**
* Sets the filter state based on the `visible` parameter.
* Closing the filters sets the state to `dismissed` on desktop,
* but `shown_on_desktop` on mobile.
*
* The calling function must be inside the `setup` function and
* must set the cookies if the return value is not `null`.
* If the filter state is changed on desktop, the `isFilterDismissed`
* 'ui' cookie value is set accordingly.
*
* @param visible - whether the filters should be visible.
* @param setCookieFn - the function that sets the app cookies
*/
setFiltersState(visible: boolean): UiStateCookie | null {
this.isFilterVisible = visible
setFiltersState(visible: boolean, setCookieFn: UiCookieSetter) {
this.innerFilterVisible = visible
if (this.isDesktopLayout) {
this.isFilterDismissed = !visible
return {
isDesktopLayout: this.isDesktopLayout,
isMobileUa: this.isMobileUa,
isFilterDismissed: this.isFilterDismissed,
}
setCookieFn(this.uiCookie)
}
return null
},
/**
* Toggles filter state. If the desktop layout is used,
* returns a new cookies value to set in the calling function.
*
* The calling function must be inside the `setup` function and
* must set the cookies if the return value is not `null`.
* Toggles filter state and saves the new state in a cookie.
*/
toggleFilters(): UiStateCookie | null {
return this.setFiltersState(!this.isFilterVisible)
toggleFilters(setCookieFn: UiCookieSetter) {
this.setFiltersState(!this.isFilterVisible, setCookieFn)
},
},
})
Loading

0 comments on commit 7037c00

Please sign in to comment.