kodadot/nft-gallery

View on GitHub
components/shared/filters/MobileFilter.vue

Summary

Maintainability
Test Coverage
<template>
  <NeoSidebar
    fullheight
    fullwidth
    overlay
    position="fixed"
    :open="open"
    :can-cancel="['escape']"
    :on-cancel="onClose"
    class="z-[1000] absolute"
    content-class="!bg-background-color"
    overlay-class="!bg-background-color opacity-[0.86]"
  >
    <div class="flex flex-col h-full">
      <div class="flex-grow">
        <NeoCommonHead
          :title="$t('general.filters')"
          @close="onClose"
        />
        <EventTypeFilter
          v-if="isCollectionActivityTab"
          data-model="store"
          expanded
        />
        <StatusFilter
          v-else
          data-model="store"
          expanded
        />
        <PriceFilter
          v-if="!isCollectionActivityTab"
          data-model="store"
          expanded
        />
        <PopularCollections
          v-if="isExploreItems"
          data-model="store"
          expanded
        />
        <AdvancedFilter
          v-if="!isCollectionActivityTab"
          data-model="store"
        />
      </div>

      <div class="buttons-container px-4 py-3 border-t bg-background-color">
        <NeoButton
          label="Reset All"
          variant="text"
          class="flex-grow min-w-36 !h-14 is-shadowless"
          @click="resetFilters"
        >
          {{ $t('general.resetAll') }}
        </NeoButton>
        <NeoButton
          variant="primary"
          no-shadow
          class="flex-grow min-w-36 h-14"
          @click="applyFilters"
        >
          {{ $t('general.apply') }}
        </NeoButton>
      </div>
    </div>
  </NeoSidebar>
</template>

<script lang="ts" setup>
import { NeoButton, NeoCommonHead, NeoSidebar } from '@kodadot1/brick'
import { useExploreFiltersStore } from '@/stores/exploreFilters'
import { useAcivityFiltersStore } from '@/stores/activityFilters'
import { usePreferencesStore } from '@/stores/preferences'
import StatusFilter from '@/components/shared/filters/modules/StatusFilter.vue'
import EventTypeFilter from '@/components/shared/filters/modules/EventTypeFilter.vue'
import PriceFilter from '@/components/shared/filters/modules/PriceFilter.vue'
import AdvancedFilter from '@/components/shared/filters/modules/AdvancedFilter.vue'
import PopularCollections from '@/components/shared/filters/modules/PopularCollections.vue'
import { getCollectionIds } from '@/utils/queryParams'

const route = useRoute()
const preferencesStore = usePreferencesStore()
const exploreFiltersStore = useExploreFiltersStore()
const activityFiltersStore = useAcivityFiltersStore()

const isCollectionActivityTab = computed(
  () => route.name === 'prefix-collection-id-activity',
)

const isExploreItems = computed(() => route.name === 'prefix-explore-items')

const { replaceUrl } = useReplaceUrl({
  resetPage: !isCollectionActivityTab.value,
})

const emit = defineEmits(['resetPage'])

const open = computed(() => preferencesStore.getMobileFilterCollapse)

const onClose = () => {
  syncFromUrl()
  closeFilterModal()
}

const closeFilterModal = () => preferencesStore.setMobileFilterCollapse(false)

const syncFromUrlOnActivityTab = () => {
  const sale = is(route.query?.sale?.toString()),
    offer = is(route.query?.offer?.toString()),
    listing = is(route.query?.listing?.toString()),
    mint = is(route.query?.mint?.toString()),
    transfer = is(route.query?.transfer?.toString())

  activityFiltersStore.setFilters({ sale, offer, listing, mint, transfer })
}
const syncFromUrlOnGrid = () => {
  const listed = route.query?.listed?.toString() === 'true',
    owned = route.query?.owned?.toString() === 'true',
    artView = route.query?.art_view?.toString() === 'true',
    collections = getCollectionIds()

  exploreFiltersStore.setListed(listed)
  exploreFiltersStore.setOwned(owned)
  exploreFiltersStore.setArtView(artView)
  exploreFiltersStore.setCollections(collections)
}

const syncFromUrl = () => {
  const min = Number(route.query?.min) || undefined,
    max = Number(route.query?.max) || undefined

  if (isCollectionActivityTab.value) {
    syncFromUrlOnActivityTab()
    activityFiltersStore.setPriceRange({ min, max })
  }
  else {
    syncFromUrlOnGrid()
    exploreFiltersStore.setPriceRange({ min, max })
  }
}

const resetFilterOnAcivityTab = () => {
  const statusDefaults = {
    sale: undefined,
    offer: undefined,
    listing: undefined,
    mint: undefined,
    transfer: undefined,
  }

  const priceDefaults = {
    min: undefined,
    max: undefined,
  }
  activityFiltersStore.setFilters(statusDefaults)

  activityFiltersStore.setPriceRange(priceDefaults)
  replaceUrl({
    ...statusDefaults,
    ...priceDefaults,
  })
}
const resetFilters = () => {
  if (isCollectionActivityTab.value) {
    resetFilterOnAcivityTab()
  }
  else {
    const statusDefaults = {
      listed: false,
      owned: false,
      artView: false,
      collections: undefined,
    }

    exploreFiltersStore.setListed(statusDefaults.listed)
    exploreFiltersStore.setOwned(statusDefaults.owned)
    exploreFiltersStore.setArtView(statusDefaults.artView)
    exploreFiltersStore.setCollections(statusDefaults.collections)

    // price
    const priceDefaults = {
      min: undefined,
      max: undefined,
    }
    exploreFiltersStore.setPriceRange(priceDefaults)

    replaceUrl({
      ...statusDefaults,
      ...priceDefaults,
    })
  }

  emit('resetPage')
  closeFilterModal()
}

const applyFilters = () => {
  // status filters
  const { artView, ...restStatusFilters } = exploreFiltersStore.getStatusFilters
  const priceRangeFilter = exploreFiltersStore.getPriceRange
  const eventTypeFilter = activityFiltersStore.getEventTypeFilters

  // apply to URL
  if (isCollectionActivityTab.value) {
    replaceUrl({ ...eventTypeFilter, ...priceRangeFilter })
  }
  else {
    replaceUrl({ art_view: artView, ...restStatusFilters, ...priceRangeFilter })
  }
  emit('resetPage')
  closeFilterModal()
}

watch(() => route.query, syncFromUrl, { immediate: true })
</script>

<style lang="scss" scoped>
@import '@/assets/styles/abstracts/variables';
.buttons-container {
  position: sticky;
  bottom: 0;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 20px;
}

.filters-header {
  min-height: $navbar-mobile-min-height;
}
</style>