feat: pagination
This commit is contained in:
		
							parent
							
								
									95f59c3faf
								
							
						
					
					
						commit
						19f86d5b89
					
				
					 4 changed files with 95 additions and 79 deletions
				
			
		| 
						 | 
				
			
			@ -3,65 +3,47 @@
 | 
			
		|||
    class="border-t border-gray-200 flex items-center justify-between max-w-2xl mx-auto p-4 sm:px-6 lg:max-w-7xl"
 | 
			
		||||
  >
 | 
			
		||||
    <div class="-mt-px w-0 flex-1 flex">
 | 
			
		||||
      <a
 | 
			
		||||
      <NuxtLink
 | 
			
		||||
        v-if="prevPageToken"
 | 
			
		||||
        :to="{ name: 'index', query: { pageToken: prevPageToken } }"
 | 
			
		||||
        href="#"
 | 
			
		||||
        class="border-t-2 border-transparent pt-4 pr-1 inline-flex items-center text-sm font-medium text-gray-500 hover:text-gray-700 hover:border-gray-300"
 | 
			
		||||
      >
 | 
			
		||||
        Previous
 | 
			
		||||
      </a>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="hidden md:-mt-px md:flex">
 | 
			
		||||
      <a
 | 
			
		||||
        href="#"
 | 
			
		||||
        class="border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 border-t-2 pt-4 px-4 inline-flex items-center text-sm font-medium"
 | 
			
		||||
      >
 | 
			
		||||
        1
 | 
			
		||||
      </a>
 | 
			
		||||
      <!-- Current: "border-pink-500 text-pink-600", Default: "border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300" -->
 | 
			
		||||
      <a
 | 
			
		||||
        href="#"
 | 
			
		||||
        class="border-brandlight text-brandlight border-t-2 pt-4 px-4 inline-flex items-center text-sm font-medium"
 | 
			
		||||
        aria-current="page"
 | 
			
		||||
      >
 | 
			
		||||
        2
 | 
			
		||||
      </a>
 | 
			
		||||
      <a
 | 
			
		||||
        href="#"
 | 
			
		||||
        class="border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 border-t-2 pt-4 px-4 inline-flex items-center text-sm font-medium"
 | 
			
		||||
      >
 | 
			
		||||
        3
 | 
			
		||||
      </a>
 | 
			
		||||
      <span
 | 
			
		||||
        class="border-transparent text-gray-500 border-t-2 pt-4 px-4 inline-flex items-center text-sm font-medium"
 | 
			
		||||
      >
 | 
			
		||||
        ...
 | 
			
		||||
      </span>
 | 
			
		||||
      <a
 | 
			
		||||
        href="#"
 | 
			
		||||
        class="border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 border-t-2 pt-4 px-4 inline-flex items-center text-sm font-medium"
 | 
			
		||||
      >
 | 
			
		||||
        8
 | 
			
		||||
      </a>
 | 
			
		||||
      <a
 | 
			
		||||
        href="#"
 | 
			
		||||
        class="border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 border-t-2 pt-4 px-4 inline-flex items-center text-sm font-medium"
 | 
			
		||||
      >
 | 
			
		||||
        9
 | 
			
		||||
      </a>
 | 
			
		||||
      <a
 | 
			
		||||
        href="#"
 | 
			
		||||
        class="border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 border-t-2 pt-4 px-4 inline-flex items-center text-sm font-medium"
 | 
			
		||||
      >
 | 
			
		||||
        10
 | 
			
		||||
      </a>
 | 
			
		||||
      </NuxtLink>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="-mt-px w-0 flex-1 flex justify-end">
 | 
			
		||||
      <a
 | 
			
		||||
      <NuxtLink
 | 
			
		||||
        v-if="nextPageToken"
 | 
			
		||||
        :to="{ name: 'index', query: { pageToken: nextPageToken } }"
 | 
			
		||||
        href="#"
 | 
			
		||||
        class="border-t-2 border-transparent pt-4 pl-1 inline-flex items-center text-sm font-medium text-gray-500 hover:text-gray-700 hover:border-gray-300"
 | 
			
		||||
      >
 | 
			
		||||
        Next
 | 
			
		||||
      </a>
 | 
			
		||||
      </NuxtLink>
 | 
			
		||||
    </div>
 | 
			
		||||
  </nav>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue'
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
  name: 'SimplePagination',
 | 
			
		||||
 | 
			
		||||
  props: {
 | 
			
		||||
    nextPageToken: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      default: () => {
 | 
			
		||||
        return ''
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    prevPageToken: {
 | 
			
		||||
      type: String,
 | 
			
		||||
      default: () => {
 | 
			
		||||
        return ''
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
})
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,5 @@
 | 
			
		|||
<template>
 | 
			
		||||
  <div
 | 
			
		||||
    id="videolist"
 | 
			
		||||
    class="max-w-2xl mx-auto py-8 px-4 sm:px-6 lg:max-w-7xl lg:px-8"
 | 
			
		||||
  >
 | 
			
		||||
  <div>
 | 
			
		||||
    <h2 class="sr-only">Videos</h2>
 | 
			
		||||
 | 
			
		||||
    <div
 | 
			
		||||
| 
						 | 
				
			
			@ -17,34 +14,18 @@
 | 
			
		|||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue'
 | 
			
		||||
const maxResults: number = 2
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
  name: 'VideoList',
 | 
			
		||||
 | 
			
		||||
  data(): {
 | 
			
		||||
    hasError: boolean
 | 
			
		||||
    videos: []
 | 
			
		||||
  } {
 | 
			
		||||
    return {
 | 
			
		||||
      hasError: false,
 | 
			
		||||
      videos: [],
 | 
			
		||||
    }
 | 
			
		||||
  props: {
 | 
			
		||||
    videos: {
 | 
			
		||||
      type: Array,
 | 
			
		||||
      default: () => {
 | 
			
		||||
        return []
 | 
			
		||||
      },
 | 
			
		||||
      required: true,
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  async fetch() {
 | 
			
		||||
    try {
 | 
			
		||||
      const response = await fetch(
 | 
			
		||||
        `https://www.googleapis.com/youtube/v3/playlistItems?part=snippet%2CcontentDetails&maxResults=${maxResults}&playlistId=${process.env.YOUTUBE_UPLOADS_PLAYLIST_ID}&key=${process.env.YOUTUBE_API_KEY}`
 | 
			
		||||
      )
 | 
			
		||||
      const json = await response.json()
 | 
			
		||||
      this.videos = json.items
 | 
			
		||||
    } catch (error: any) {
 | 
			
		||||
      this.hasError = true
 | 
			
		||||
      throw new Error(error)
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  fetchOnServer: true,
 | 
			
		||||
})
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -112,6 +112,10 @@ export default {
 | 
			
		|||
    ],
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  router: {
 | 
			
		||||
    routes: [{ path: '/:pageToken', props: true }],
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  // Global CSS: https://go.nuxtjs.dev/config-css
 | 
			
		||||
  css: [],
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,8 +6,17 @@
 | 
			
		|||
    <main>
 | 
			
		||||
      <IndexHero />
 | 
			
		||||
      <div class="bg-gray-200 pb-12">
 | 
			
		||||
        <VideosList />
 | 
			
		||||
        <SimplePagination />
 | 
			
		||||
        <div class="max-w-2xl mx-auto py-8 px-4 sm:px-6 lg:max-w-7xl lg:px-8">
 | 
			
		||||
          <p v-if="$fetchState.pending">Fetching videos...</p>
 | 
			
		||||
          <p v-else-if="$fetchState.error">An error occurred :(</p>
 | 
			
		||||
          <div v-else id="videolist">
 | 
			
		||||
            <VideosList :videos="videos" />
 | 
			
		||||
            <SimplePagination
 | 
			
		||||
              :prev-page-token="prevPageToken"
 | 
			
		||||
              :next-page-token="nextPageToken"
 | 
			
		||||
            />
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </main>
 | 
			
		||||
  </div>
 | 
			
		||||
| 
						 | 
				
			
			@ -15,8 +24,48 @@
 | 
			
		|||
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import Vue from 'vue'
 | 
			
		||||
const maxResults: number = 12
 | 
			
		||||
 | 
			
		||||
export default Vue.extend({
 | 
			
		||||
  name: 'IndexPage',
 | 
			
		||||
 | 
			
		||||
  data(): {
 | 
			
		||||
    videos: []
 | 
			
		||||
    prevPageToken: string
 | 
			
		||||
    nextPageToken: string
 | 
			
		||||
  } {
 | 
			
		||||
    return {
 | 
			
		||||
      videos: [],
 | 
			
		||||
      prevPageToken: '',
 | 
			
		||||
      nextPageToken: '',
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  async fetch() {
 | 
			
		||||
    try {
 | 
			
		||||
      let requestUrl =
 | 
			
		||||
        'https://www.googleapis.com/youtube/v3/playlistItems?part=snippet%2CcontentDetails'
 | 
			
		||||
      requestUrl += `&maxResults=${maxResults}`
 | 
			
		||||
      requestUrl += `&playlistId=${process.env.NUXT_ENV_YOUTUBE_UPLOADS_PLAYLIST_ID}`
 | 
			
		||||
      requestUrl += `&key=${process.env.NUXT_ENV_YOUTUBE_API_KEY}`
 | 
			
		||||
      if (this.$route.query.pageToken)
 | 
			
		||||
        requestUrl += `&pageToken=${this.$route.query.pageToken}`
 | 
			
		||||
 | 
			
		||||
      const response = await fetch(requestUrl)
 | 
			
		||||
      const json = await response.json()
 | 
			
		||||
 | 
			
		||||
      this.prevPageToken = json.prevPageToken
 | 
			
		||||
      this.nextPageToken = json.nextPageToken
 | 
			
		||||
      this.videos = json.items
 | 
			
		||||
    } catch (error: any) {
 | 
			
		||||
      throw new Error(error)
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  watch: {
 | 
			
		||||
    '$route.query': '$fetch',
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  fetchOnServer: true,
 | 
			
		||||
})
 | 
			
		||||
</script>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		
		Reference in a new issue