Pular para conteúdo

Tutorial TMDB

A API TMDB (The Movie Database API)

A API TMDB é uma API REST que fornece informações sobre filmes, séries e programas de TV. Ela é uma API pública e gratuita, que pode ser acessada através do endereço https://www.themoviedb.org/documentation/api.

Para utilizar a API, é necessário criar uma conta no site https://www.themoviedb.org/. Após criar a conta, é necessário criar uma chave de acesso (API Key). Para isso, acesse o menu Settings (Configurações) e, em seguida, API. Clique no botão Create e, em seguida, Developer. Dê um nome para a chave e clique em Create. A chave será criada e será exibida na tela. Copie a chave e guarde-a em um local seguro. Essa chave será utilizada para acessar a API. A chave que queremos acesso é a que está no campo Token de Leitura da API.

Conhecendo a API

A API TMDB fornece diversas informações sobre filmes, séries e programas de TV. Para acessar essas informações, é necessário fazer uma requisição HTTP para o servidor da API. A API fornece diversas informações, como a lista de filmes em cartaz, a lista de filmes mais populares, a lista de filmes mais bem avaliados, etc. Além disso, é possível obter informações sobre um filme específico, como o título, a sinopse, o elenco, etc.

Na documentação da API, em [https://developer.themoviedb.org/reference/intro/getting-started] (API Reference), é possível encontrar no menu lateral os seguintes tópicos, entre outro:

  • Account (Conta): informações sobre como obter detalhes da conta, favoritos, filmes avaliados, etc.
  • Authentication (Autenticação): informações sobre como obter solicitar token de acesso, criar uma sessão, validar uma chave, etc.
  • Certifications (Certificações): informações sobre as classificações indicativas de filmes, séries e programas de TV.
  • ...
  • Companies (Empresas): informações sobre as empresas que produzem filmes, séries e programas de TV.
  • Discover (Descobrir): informações sobre como descobrir filmes, séries e programas de TV.
  • Find (Encontrar): informações sobre como encontrar filmes, séries e programas de TV por ID.
  • Genres (Gêneros): informações sobre os gêneros de filmes, séries e programas de TV.
  • ...
  • Movies Lists (Listas de filmes): informações sobre como obter listas de filmes em cartaz, populares, melhor avaliados e em lançamento breve.
  • Movies (Filmes): informações sobre como obter detalhes de um filme, elenco, imagens, etc.
  • E muitos outros.

Buscando os gêneros de filmes e programas de TV

Para entender o funcionamento da API, vamos buscar na documentação pelo item Genres -> Movie List. Ao acessar a documentação, é possível perceber que a API aceita como parâmetro (Query Params) apenas o item "Language". Também na documentação da resposta que será enviada pela API (Response) fica definido que o retorno será um objeto JSON com os seguintes campos:

  • genres: um array de objetos JSON com os seguintes campos:
  • id: o id do gênero
  • name: o nome do gênero

Também, é possível identificar que o endpoint consultado será o seguinte:

https://api.themoviedb.org/3/genre/movie/list

Nesse caso, case se deseje buscar os gêneros em língua portuguesa, pode-se utilizar o seguinte endpoint:

https://api.themoviedb.org/3/genre/movie/list?language=pt-BR

visto que language é um dos parâmetros aceitos pela API.

Para testar o seu uso pode ser utilizado o próprio painel lateral da documentação da API. Para isso, basta preencher o campo Header no item Authorization e clicar no botão Try it!. O resultado será exibido na tela.

Também pode ser utilizado a extensão RapidAPI do Visual Studio Code, ou qualquer outra extensão ou ferramenta para testar APIs REST. Nesse caso é essencial informar o Token de Leitura da API. Para isso, em Headers adicione um Header Authorization com o valor Bearer {token}, onde {token} é o token de leitura da API.

Buscando os filmes em cartaz

Para buscar os filmes em cartaz, vamos buscar na documentação pelo item Movie Lists -> Now Playing. Ao acessar a documentação, é possível perceber que a API aceita como parâmetros (Query Params) os seguintes itens:

  • Language: o idioma dos dados retornados
  • Page: o número da página a ser retornada
  • Region: o país a ser considerado para a busca. No caso do Brasil, esse código é BR.

Também na documentação da resposta que será enviada pela API (Response) fica definido que o retorno será um objeto JSON com os seguintes campos:

  • dates: um objeto JSON com os seguintes campos:
  • maximum: a data máxima de lançamento dos filmes
  • minimum: a data mínima de lançamento dos filmes
  • page: o número da página retornada
  • results: um array de objetos JSON com os seguintes campos:
  • adult: um booleano que indica se o filme é para adultos
  • backdrop_path: o caminho para a imagem de fundo do filme
  • genre_ids: um array de inteiros com os ids dos gêneros do filme
  • id: o id do filme
  • original_language: o idioma original do filme
  • original_title: o título original do filme
  • overview: a sinopse do filme
  • popularity: a popularidade do filme
  • poster_path: o caminho para o poster do filme
  • release_date: a data de lançamento do filme
  • title: o título do filme
  • video: um booleano que indica se o filme tem vídeo
  • vote_average: a média de votos do filme
  • vote_count: o número de votos do filme
  • total_pages: o número total de páginas
  • total_results: o número total de resultados

Também, é possível identificar que o endpoint consultado será o seguinte:

https://api.themoviedb.org/3/movie/now_playing

Nesse caso, case se deseje buscar os filmes em cartaz no Brasil, com retorno em língua portuguesa, pode-se utilizar o seguinte endpoint:

https://api.themoviedb.org/3/movie/now_playing?language=pt-BR&region=BR

Da mesma forma que o exemplo anterior, é possível testar o uso do endpoint no painel lateral da documentação da API, ou utilizando uma extensão ou ferramenta para testar APIs REST. Nesse caso é essencial informar o Token de Leitura da API. Para isso, em Headers adicione um Header Authorization com o valor Bearer {token}, onde {token} é o token de leitura da API.

Recuperando as imagens

A API TMDB fornece as imagens dos filmes, séries e programas de TV em diversos tamanhos. Para recuperar as imagens, é necessário utilizar o endpoint https://image.tmdb.org/t/p/{size}/{path}, onde {size} é o tamanho da imagem e {path} é o caminho da imagem. O tamanho da imagem pode ser um dos seguintes:

  • w92: 92px de largura
  • w154: 154px de largura
  • w185: 185px de largura
  • w342: 342px de largura
  • w500: 500px de largura
  • w780: 780px de largura
  • original: tamanho original da imagem

Já o {path} é o caminho da imagem que se deseja buscar. Por exemplo, no caso da listagem dos filmes em cartaz, apresentado anteriormente, o campo poster_path contém esse valor.

Outros recursos

É muito importante navegar pela documentação da API TMDB para conhecer os recursos disponíveis. Além disso, é possível testar o uso da API diretamente na documentação, o que facilita o entendimento do funcionamento da API.

Projeto TMDB no Vue

O objetivo deste tutorial é apresentar como utilizar a API TMDB (The Movie Database API) para buscar informações sobre filmes, séries e programas de TV. Para isso, será utilizado o framework Vue.js e a biblioteca Axios.

A construção será progressiva, de forma que novas formas de organizar os arquivos e de utilizar o Vue.js serão apresentadas ao longo do tutorial.

Criação do projeto

Para criar o projeto, vamos utilizar o Vite. Para isso, vamos criar uma pasta para esse projeto, por exemplo com o nome filmes-tmdb. Em seguida, vamos abrir essa pasta no Visual Studio Code e, no terminal, vamos executar o seguinte comando:

npm init vue@latest .

Vamos responder às perguntas como já apresentado em aulas anteriores. Neste momento, não adicione ainda nem o Vue Router e nem o Pinia. Faremos isso mais adiante.

Instalação do Axios

Para instalar o Axios, vamos executar o seguinte comando no terminal:

npm install axios

Buscando os gêneros dos filmes e programas de TV

Nesta primeira etapa, vamos fazer apenas uma alteração na página principal para listar os gêneros de filmes e programas de TV.

Inicialmente, vamos abrir o arquivo App.vue, e alterá-lo para o seguinte conteúdo:

<script setup>
  import { ref, onMounted } from 'vue';
  import axios from 'axios';

  const moviesGenres = ref([]);
  const TVGenres = ref([]);

  onMounted(async () => {
    let response = await axios.get(
      'https://api.themoviedb.org/3/genre/movie/list?language=pt-BR',
      {
        headers: {
          Authorization: `Bearer AQUI_COLOCAR O TOKEN DE LEITURA DA API`,
        },
      },
    );
    moviesGenres.value = response.data.genres;
    response = await axios.get(
      'https://api.themoviedb.org/3/genre/tv/list?language=pt-BR',
      {
        headers: {
          Authorization: `Bearer AQUI_COLOCAR O TOKEN DE LEITURA DA API`,
        },
      },
    );
    TVGenres.value = response.data.genres;
  });
</script>

<template>
  <h1>Gêneros de filmes</h1>
  <ul>
    <li v-for="genre in moviesGenres" :key="genre.id">{{ genre.name }}</li>
  </ul>
  <hr />
  <h1>Gêneros de programas de TV</h1>
  <ul>
    <li v-for="genre in TVGenres" :key="genre.id">{{ genre.name }}</li>
  </ul>
</template>

Iniciamos importando o ref e o onMounted do Vue e o axios. Em seguida, criamos duas variáveis reativas, moviesGenres e TVGenres, que serão utilizadas para armazenar os gêneros de filmes e de programas de TV, respectivamente.

Em seguida, utilizamos o onMounted para executar uma função quando o componente for montado. Essa função é assíncrona, pois utilizaremos o await para aguardar o retorno das requisições à API. Criamos uma variável para armazenar o retorno da chamada à API, e utilizamos o await para aguardar o retorno da chamada à API. Em seguida, armazenamos os gêneros de filmes na variável moviesGenres e os gêneros de programas de TV na variável TVGenres.

Perceba que armazenamos o retorno em uma variável chamada response. Essa variável é criada com o let pois será reutilizada para armazenar o retorno da chamada à API de programas de TV. O retorno da chamada à API contém um objeto com os seguintes campos:

  • config: um objeto com a configuração da requisição
  • data: o corpo da resposta
  • headers: um objeto com os cabeçalhos da resposta
  • status: o código de status da resposta
  • statusText: a mensagem de status da resposta

Por isso utilizamos response.data para acessar apenas o corpo da resposta. Nesse caso, conforme a documentação da API, o corpo da resposta é um objeto JSON com os seguintes campos:

  • genres: um array de objetos JSON com os seguintes campos:
  • id: o id do gênero
  • name: o nome do gênero

Por isso, utilizamos response.data.genres para acessar apenas o array de gêneros.

Configurando o Axios

Note que no código acima, a chave de leitura da API foi inserida diretamente no código. Isso não é uma boa prática, pois a chave de leitura é um dado sensível que não deve ser exposto. Esse é um dado sensível que deve ser armazenado em um arquivo de configuração, que não deve ser enviado para o repositório. Vamos resolver esse detalhe em aulas posteriores.

No entanto, um outro detalhe no código acima é que em todas as chamadas à API, foi necessário informar o endpoint completo, bem como o cabeçalho Authorization. Para evitar repetir essas informações em todas as chamadas à API, vamos configurar o Axios para que ele já inclua essas informações em todas as chamadas à API.

Para isso, vamos criar um arquivo chamado axios.js na pasta src/plugins (Se a pasta plugins não existir, crie dentro da pasta src). Em seguida, vamos adicionar o seguinte conteúdo:

import axios from 'axios';

const api = axios.create({
  baseURL: 'https://api.themoviedb.org/3/',
  headers: {
    Authorization: `Bearer COLOQUE AQUI A CHAVE DE LEITURA DA API`,
  },
});

export default api;

Nesse arquivo, importamos o Axios e criamos uma instância do Axios com o método create. Esse método recebe um objeto com as configurações da instância. Nesse caso, definimos a URL base da API e o cabeçalho Authorization. Em seguida, exportamos essa instância, chamada de api.

Utilizando o Axios modificado nas chamadas à API

Agora, vamos alterar o arquivo App.vue para utilizar o Axios modificado. Para isso, vamos alterar a parte de script do arquivo App.vue para o seguinte:

<script setup>
  import { ref, onMounted } from 'vue';
  import api from './plugins/axios';

  const moviesGenres = ref([]);
  const TVGenres = ref([]);

  onMounted(async () => {
    let response = await api.get('genre/movie/list?language=pt-BR');
    moviesGenres.value = response.data.genres;
    response = await api.get('genre/tv/list?language=pt-BR');
    TVGenres.value = response.data.genres;
  });
</script>

Note que o arquivo ficou mais sucinto, facilitando a leitura e a manutenção do código.

Adicionar o Vue-router

Nesta etapa, vamos adicionar o Vue-router ao projeto. Para isso, vamos executar o seguinte comando no terminal:

npm install vue-router

Em seguida, vamos criar o arquivo src/router/index.js com o seguinte conteúdo:

import { createRouter, createWebHistory } from 'vue-router';

const routes = [
  {
    path: '/',
    name: 'Home',
    component: () => import('../views/Home.vue'),
  },
  {
    path: '/filmes',
    name: 'Movies',
    component: () => import('../views/MoviesView.vue'),
  },
  {
    path: '/tv',
    name: 'TV',
    component: () => import('../views/TvView.vue'),
  },
];

const router = createRouter({
  history: createWebHistory(),
  routes,
});

export default router;

No exemplo acima, estamos criando um roteador com três rotas:

  • /: rota para a página inicial
  • /filmes: rota para a página de filmes
  • /tv: rota para a página de programas de TV

No arquivo src/main.js, vamos importar o Vue-router e adicionar o router ao projeto. Para substitua o conteúdo do arquivo pelo seguinte:

import { createApp } from 'vue';
import App from './App.vue';
import router from './router';

const app = createApp(App);
app.use(router);

app.mount('#app');

Nesse caso, estamos adicionando o suporte ao Vue-router ao projeto e, em seguida, montando o aplicativo.

Criando a página Home

Nesta etapa, vamos criar a página Home. Para isso, vamos criar o arquivo src/views/Home.vue com o seguinte conteúdo:

<template>
  <div>
    <h1>Home</h1>
  </div>
</template>

Nesse caso é apenas uma página com o título "Home", que será aprimorara ao longo do tutorial.

Separando a listagem de gêneros em duas views

Nesta etapa, vamos separar a listagem de gêneros em duas views: uma para filmes e outra para programas de TV.

Criando a página para gêneros de filmes

Para criar a página para gêneros de filmes, vamos criar o arquivo src/views/MoviesView.vue com o seguinte conteúdo:

<script setup>
  import { ref, onMounted } from 'vue';
  import api from '@/plugins/axios';

  const genres = ref([]);

  onMounted(async () => {
    const response = await api.get('genre/movie/list?language=pt-BR');
    genres.value = response.data.genres;
  });
</script>
<template>
  <div>
    <h1>Gêneros de filmes</h1>
    <ul>
      <li v-for="genre in genres" :key="genre.id">{{ genre.name }}</li>
    </ul>
  </div>
</template>

Note que estamos utilizando o axios como um plugin, conforme apresentado em aulas anteriores. Também aproveitamos a mesma parte de código que já tinha sido anteriormente desenvolvida.

Criando a página para gêneros de programas de TV

Para criar a página para gêneros de programas de TV, vamos criar o arquivo src/views/TvView.vue com o seguinte conteúdo:

<script setup>
  import { ref, onMounted } from 'vue';
  import api from '@/plugins/axios';

  const genres = ref([]);

  onMounted(async () => {
    const response = await api.get('genre/tv/list?language=pt-BR');
    genres.value = response.data.genres;
  });
</script>

<template>
  <div>
    <h1>Gêneros de programas de TV</h1>
    <ul>
      <li v-for="genre in genres" :key="genre.id">{{ genre.name }}</li>
    </ul>
  </div>
</template>

Note que o exemplo é muito semelhante ao anterior, com a diferença de que estamos buscando os gêneros de programas de TV.

Adicionando o menu de navegação

Nesta etapa, vamos adicionar o menu de navegação. Para isso, vamos alterar o arquivo src/App.vue para o seguinte conteúdo:

<script setup></script>

<template>
  <div>
    <nav>
      <router-link to="/">Home</router-link>
      <router-link to="/filmes">Filmes</router-link>
      <router-link to="/tv">Programas de TV</router-link>
    </nav>
    <router-view />
  </div>
</template>

<style scoped>
  nav {
    display: flex;
    justify-content: flex-start;
    margin-bottom: 1rem;
    column-gap: 2rem;
  }
</style>

Note que estamos utilizando o componente RouterLink para criar os links de navegação. Também estamos utilizando o componente RouterView para indicar onde o conteúdo da página será exibido. Ainda, fizemos uma pequena estilização para o menu de navegação, para que os links fiquem alinhados à esquerda e com um espaçamento entre eles. Note que usamos o atributo scoped para que o estilo seja aplicado apenas ao componente atual.

Ajustes de estilos CSS

Para criar um arquivo de estilos, vamos criar um arquivo src/assets/main.css com o seguinte conteúdo:

*,
*::before,
*::after {
  box-sizing: border-box;
  margin: 0;
}

body {
  min-height: 100vh;
  line-height: 1.6;
  font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
    Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
  font-size: 15px;
  text-rendering: optimizeLegibility;
}

Vamos detalhar o que está sendo feito no arquivo acima:

  • A primeira regra CSS define que todos os elementos, incluindo os pseudo-elementos ::before e ::after, devem ter:
  • a propriedade box-sizing com o valor border-box: define que o tamanho de um elemento deve considerar o tamanho do conteúdo, o padding e a borda.
  • a propriedade margin com o valor 0.
  • A segunda regra CSS define que o elemento body deve ter:
  • a propriedade min-height com o valor 100vh: define que a altura mínima do elemento deve ser 100% da altura da tela.
  • a propriedade line-height com o valor 1.6: define o espaçamento entre linhas.
  • a propriedade font-family com uma lista de fontes: define a lista de fontes a serem utilizadas.
  • a propriedade font-size com o valor 15px: define o tamanho da fonte.
  • a propriedade text-rendering com o valor optimizeLegibility: define que o texto deve ser renderizado de forma a melhorar a legibilidade.

Vamos alterar agora o arquivo main.js para importar o arquivo main.css:

import './assets/main.css';
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';

const app = createApp(App);
app.use(router);

app.mount('#app');

Note que apenas importamos o arquivo main.css que será incluído no arquivo index.html automaticamente. Esta foi a única linha alterada no arquivo main.js. Além disso, as regras CSS definidas no arquivo main.css serão aplicadas a todos os elementos da aplicação.

Adicionando um estilo no App.vue

Vamos adicionar um estilo no componente App.vue para inserir um pequeno cabeçalho. Para isso, vamos primeiramente alterar o bloco template do arquivo App.vue, para o seguinte conteúdo:

<template>
  <header>
    <nav>
      <router-link to="/">Home</router-link>
      <router-link to="/filmes">Filmes</router-link>
      <router-link to="/tv">Programas de TV</router-link>
    </nav>
  </header>
  <main>
    <router-view />
  </main>
</template>

Note que adicionamos um cabeçalho com o menu de navegação que já estava presente no arquivo App.vue e movemos o bloco router-view para dentro do elemento main.

Em seguida, vamos adicionar o seguinte estilo no bloco style do arquivo App.vue:

<style scoped>
header {
  height: 3rem;
  display: flex;
  background-color: black;
  color: #fff;
  font-size: 1.2rem;
  padding-left: 2rem;
}

nav {
  column-gap: 2rem;
  margin-bottom: 0;
  display: flex;
  align-items: center;
}

nav a {
  text-decoration: none;
  color: #fff;
}
</style>

Nesse caso, criamos três regras CSS:

  • para a tag header
  • a propriedade height com o valor 3rem: define a altura do elemento como 3 vezes o tamanho da fonte.
  • a propriedade display com o valor flex: define que o elemento deve ser exibido como um flex container.
  • a propriedade background-color com o valor black: define a cor de fundo do elemento como preto.
  • a propriedade color com o valor #fff: define a cor do texto como branco.
  • a propriedade font-size com o valor 1.2rem: define o tamanho da fonte como 1.2 vezes o tamanho da fonte, que foi sido definido no arquivo main.css.
  • a propriedade padding-left com o valor 2rem: define o espaçamento interno esquerdo como 2 vezes o tamanho da fonte.
  • para a tag nav
  • a propriedade column-gap com o valor 2rem: define o espaçamento entre colunas como 2 vezes o tamanho da fonte.
  • a propriedade margin-bottom com o valor 0: define o espaçamento externo inferior como 0.
  • a propriedade display com o valor flex: define que o elemento deve ser exibido como um flex container.
  • a propriedade align-items com o valor center: define que os itens devem ser alinhados verticalmente ao centro.
  • para a tag a dentro de nav
  • a propriedade text-decoration com o valor none: define que o texto não deve ter decoração.
  • a propriedade color com o valor #fff: define a cor do texto como branco.

Adicionando um estilo no MoviesView.vue

Vamos adicionar um estilo no componente MoviesView.vue para apresentar a lista de gêneros de forma mais agradável. Para isso, vamos alterar o bloco template do arquivo MoviesView.vue, para o seguinte conteúdo:

<template>
  <h1>Filmes</h1>
  <ul class="genre-list">
    <li v-for="genre in genres" :key="genre.id" class="genre-item">
      {{ genre.name }}
    </li>
  </ul>
</template>

Note que nesse caso, alteramos o título h1 e adicionamos uma classe genre-list na lista ul e uma classe genre-item nos itens da lista (li).

Em seguida, vamos adicionar o seguinte estilo no bloco style do arquivo MoviesView.vue:

<style scoped>
.genre-list {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 2rem;
  list-style: none;
  padding: 0;
}

.genre-item {
  background-color: #387250;
  border-radius: 1rem;
  padding: 0.5rem 1rem;
  color: #fff;
}

.genre-item:hover {
  cursor: pointer;
  background-color: #4e9e5f;
  box-shadow: 0 0 0.5rem #387250;
}
</style>

Nesse caso, criamos três regras CSS:

  • para a classe .genre-list:
  • a propriedade display com o valor flex: define que o elemento deve ser exibido como um flex container.
  • a propriedade justify-content com o valor center: define que os itens devem ser alinhados horizontalmente ao centro.
  • a propriedade flex-wrap com o valor wrap: define que os itens devem ser quebrados em linhas.
  • a propriedade gap com o valor 2rem: define o espaçamento entre os itens como 2 vezes o tamanho da fonte.
  • a propriedade list-style com o valor none: define que a lista não deve ter marcadores.
  • para a classe .genre-item:
  • a propriedade background-color com o valor #387250: define a cor de fundo do elemento como verde.
  • a propriedade border-radius com o valor 1rem: define o raio da borda como 1 vez o tamanho da fonte.
  • a propriedade padding com o valor 0.5rem 1rem: define o espaçamento interno como 0.5 vezes o tamanho da fonte na vertical e 1 vez o tamanho da fonte na horizontal.
  • a propriedade color com o valor #fff: define a cor do texto como branco.
  • para a pseudo-classe .genre-item:hover, quando o mouse estiver sobre o elemento:
  • a propriedade cursor com o valor pointer: define que o cursor do mouse deve ser alterado para um ponteiro.
  • a propriedade background-color com o valor #4e9e5f: define a cor de fundo do elemento como verde mais escuro.
  • a propriedade box-shadow com o valor 0 0 0.5rem #387250: define uma sombra ao redor do elemento.

Adicionando um estilo no TvView.vue

As alterações no arquivo TvView.vue são muito semelhantes às alterações no arquivo MoviesView.vue. Vamos alterar o bloco template e style do arquivo TvView.vue, para o seguinte conteúdo:

<template>
  <h1>Programas de TV</h1>
  <ul class="genre-list">
    <li v-for="genre in genres" :key="genre.id" class="genre-item">
      {{ genre.name }}
    </li>
  </ul>
</template>

<style scoped>
  .genre-list {
    display: flex;
    justify-content: center;
    flex-wrap: wrap;
    gap: 2rem;
    list-style: none;
    padding: 0;
  }

  .genre-item {
    background-color: #5d6424;
    border-radius: 1rem;
    padding: 0.5rem 1rem;
    align-self: center;
    color: #fff;
    display: flex;
    justify-content: center;
  }

  .genre-item:hover {
    cursor: pointer;
    background-color: #7d8a2e;
    box-shadow: 0 0 0.5rem #5d6424;
  }
</style>

Note que as únicas diferenças em relação ao exemplo do MovieView.vue são as cores utilizadas.

Listando filmes

Vamos agora listar os filmes ao clicar no gênero. Para isso, vamos alterar o componente MoviesView.vue para que ele liste os filmes ao clicar no gênero. Inicialmente, vamos adicionar um listener ao evento click do elemento li que contém o gênero. Para isso, vamos alterar o bloco template do arquivo MoviesView.vue para o seguinte conteúdo:

<template>
  ...
  <li
    v-for="genre in genres"
    :key="genre.id"
    @click="listMovies(genre.id)"
    class="genre-item"
  >
    {{ genre.name }}
  </li>
  ...
</template>

Note que adicionamos o listener ao evento click do elemento li que contém o gênero. Esse listener chama o método listMovies passando o id do gênero como parâmetro. Além disso, as demais partes do código permanecem inalteradas. Agora, vamos adicionar o método listMovies no bloco script do arquivo MoviesView.vue:

<script setup>
  ...
  const movies = ref([]);

  const listMovies = async (genreId) => {
      const response = await api.get('discover/movie', {
          params: {
              with_genres: genreId,
              language: 'pt-BR'
          }
      });
      movies.value = response.data.results
  };
  ...
</script>

Neste exemplo, criamos uma variável reativa movies que armazena a lista de filmes. Além disso, criamos o método listMovies que recebe o id do gênero como parâmetro (chamado de genreId). Esse método faz uma requisição para a rota discover/movie da API do TMDB passando o genreId como parâmetro. Em seguida, o método atribui a lista de filmes retornada pela API à variável reativa movies. As demais partes do código permanecem inalteradas. Agora, vamos alterar o bloco template do arquivo MoviesView.vue, adicionando o seguinte conteúdo, depois de fechar a tag </ul>:

<div class="movie-list">
  <div v-for="movie in movies" :key="movie.id" class="movie-card">
    <img
      :src="`https://image.tmdb.org/t/p/w500${movie.poster_path}`"
      :alt="movie.title"
    />
    <div class="movie-details">
      <p class="movie-title">{{ movie.title }}</p>
      <p class="movie-release-date">{{ movie.release_date }}</p>
      <p class="movie-genres">{{ movie.genre_ids }}</p>
    </div>
  </div>
</div>

Neste caso, criamos um elemento div com a classe movie-list que contém um elemento div para cada filme (comportamento garantido pelo v-for). Cada elemento div com a classe movie-card contém:

  • uma imagem com o cartaz do filme,
  • um elemento div com a classe movie-details que contém:
  • o título,
  • a data de lançamento e
  • os gêneros do filme. Note que utilizamos a interpolação para definir o valor do atributo src da imagem. Além disso, utilizamos a interpolação para definir o valor dos textos dos elementos p que contém o título, a data de lançamento e os gêneros do filme.

Agora, vamos adicionar o seguinte estilo no bloco style do arquivo MoviesView.vue:

.movie-list {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
}

.movie-card {
  width: 15rem;
  height: 30rem;
  border-radius: 0.5rem;
  overflow: hidden;
  box-shadow: 0 0 0.5rem #000;
}

.movie-card img {
  width: 100%;
  height: 20rem;
  border-radius: 0.5rem;
  box-shadow: 0 0 0.5rem #000;
}

.movie-details {
  padding: 0 0.5rem;
}

.movie-title {
  font-size: 1.1rem;
  font-weight: bold;
  line-height: 1.3rem;
  height: 3.2rem;
}

Neste caso, definimos 5 regras de estilos CSS:

  • para o elemento div com a classe movie-list
  • a propriedade display com o valor flex: define que o elemento deve ser exibido como um flex container.
  • a propriedade flex-wrap com o valor wrap: define que os elementos devem ser exibidos em várias linhas.
  • a propriedade gap com o valor 1rem: define o espaçamento entre os elementos como 1 vez o tamanho da fonte.
  • para o elemento div com a classe movie-card
  • a propriedade width com o valor 15rem: define a largura do elemento como 15 vezes o tamanho da fonte.
  • a propriedade height com o valor 30rem: define a altura do elemento como 30 vezes o tamanho da fonte.
  • a propriedade border-radius com o valor 0.5rem: define o raio da borda como 0.5 vezes o tamanho da fonte.
  • a propriedade overflow com o valor hidden: define que o conteúdo que ultrapassar o tamanho do elemento deve ser ocultado.
  • a propriedade box-shadow com o valor 0 0 0.5rem #000: define um sombreamento de 0.5 vezes o tamanho da fonte.
  • para a imagem
  • a propriedade width com o valor 100%: define a largura da imagem como 100% do tamanho do elemento pai.
  • a propriedade height com o valor 20rem: define a altura da imagem como 20 vezes o tamanho da fonte.
  • a propriedade border-radius com o valor 0.5rem: define o raio da borda como 0.5 vezes o tamanho da fonte.
  • a propriedade box-shadow com o valor 0 0 0.5rem #000: define um sombreamento de 0.5 vezes o tamanho da fonte.
  • para o elemento div com a classe movie-details
  • a propriedade padding com o valor 0 0.5rem: define o espaçamento interno como 0 vezes o tamanho da fonte na vertical e 0.5 vezes o tamanho da fonte na horizontal.
  • para o elemento p com a classe movie-title
  • a propriedade font-size com o valor 1.1rem: define o tamanho da fonte como 1.1 vezes o tamanho da fonte.
  • a propriedade font-weight com o valor bold: define o peso da fonte como negrito.
  • a propriedade line-height com o valor 1.3rem: define a altura da linha como 1.3 vezes o tamanho da fonte.
  • a propriedade height com o valor 3.2rem: define a altura do elemento como 3.2 vezes o tamanho da fonte.

Por fim, faremos uma pequena alteração na definação de estilo da classe .genre-list para adicionar uma margem inferior, como segue:

.genre-list {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 2rem;
  list-style: none;
  margin-bottom: 2rem;
}

Note que nesse caso adicionamos a propriedade margin-bottom com o valor 2rem para definir uma margem inferior de 2 vezes o tamanho da fonte.

Exercícios

Fazer a listagem de programas de TV ao clicar no gênero. Note que no caso dos programas de TV, a rota da API é discover/tv e o parâmetro para filtrar por gênero é with_genres. Além disso, o nome do campo que contém o título do programa de TV é name, o nome original é original_name e o nome do campo que contém a data de lançamento é first_air_date.

Visualizar ícone de carregando

Para facilitar a visualização do ícone de carregando, poderíamos criar um componente para isso, com toda a lógica necessária para exibir o ícone de carregando, bem como os estilos de CSS. Contudo, vamos fazer de uma forma mais simples, usando um componente público já desenvolvido chamado vue-loading-overlay. Para isso, vamos instalar esse componente com o seguinte comando no terminal:

npm install vue-loading-overlay

Em seguida, vamos abrir o arquivo main.js e adicionar o seguinte conteúdo na primeira linha:

import 'vue-loading-overlay/dist/css/index.css';

O restante do arquivo permanece inalterado.

Agora, vamos abrir o arquivo MoviesView.vue e adicionar o seguinte conteúdo no bloco template, depois da tag </ul>:

<loading v-model:active="isLoading" is-full-page />

Neste caso, adicionamos o componente loading que recebe o valor da variável isLoading como parâmetro. Além disso, definimos que o componente deve ocupar toda a página com o parâmetro is-full-page. Agora, vamos adicionar o seguinte conteúdo no bloco script:

<script setup>
  import Loading from 'vue-loading-overlay';

  const isLoading = ref(false);
</script>

Note que importamos o componente Loading do pacote vue-loading-overlay e criamos uma variável reativa isLoading que indica se o ícone de carregando deve ser exibido ou não. Sugiro que você organize o código de forma que os imports fiquem no início do bloco script e as variáveis reativas fiquem na sequência, antes da definição das demais funções.

Agora, vamos alterar o método listMovies para que ele altere o valor da variável isLoading antes e depois de fazer a requisição para a API, como segue:

<script setup>
...
const listMovies = async (genreId) => {
  isLoading.value = true;
  const response = await api.get('discover/movie', {
    params: {
      with_genres: genreId,
      language: 'pt-BR'
    }
  });
  movies.value = response.data.results
  isLoading.value = false;
};
...

Note que apenas fizemos uma pequena alteração na função, adicionando a linha isLoading.value = true; antes da chamada à API e a linha isLoading.value = false; depois da chamada à API.

Mostrar a tag de gêneros

Nos exemplos anteriores, foram exibidos apenas os ids dos gêneros na listagem de filmes (e programas de TV). Neste exemplo, vamos exibir a tag de cada gênero na listagem. Para isso, vamos alterar o método listMovies para que ele substitua o id do gênero pelo nome do gênero.

Inicialmente, vamos criar uma função que retorna o nome do gênero a partir do id do gênero. Para isso, vamos abrir o arquivo MoviesView.vue e adicionar o seguinte conteúdo no bloco script:

<script setup>

const getGenreName = (id) => genres.value.find((genre) => genre.id === id).name

Note que a função getGenreName recebe o id do gênero como parâmetro e retorna o nome do gênero. Para isso, usamos a função find do JavaScript para encontrar o gênero com o id informado e retornar o nome do gênero.

Esse código poderia ser escrito de uma forma diferente, um pouco mais detalhada, como segue:

<script setup>

  function getGenreName(id) {
    const genero = genres.value.find((genre) => genre.id === id);
    return genero.name;
  }

Você pode escolher a forma que preferir. O importante é entender o que está sendo feito e usar apenas uma das formas.

Agora, vamos alterar a listagem de filmes para que ele substitua o id do gênero pelo nome do gênero. Para isso, vamos abrir o arquivo MoviesView.vue e alterar o seguinte conteúdo no bloco template:

<p class="movie-genres">
  <span
    v-for="genre_id in movie.genre_ids"
    :key="genre_id"
    @click="listMovies(genre_id)"
  >
    {{ getGenreName(genre_id) }}
  </span>
</p>

Note que localizamos a tag p com a classe movie-genres e substituímos o conteúdo {{ movie.genre_ids }} pela tag span acima. No caso do exemplo, a tag span é criada para cada id de gênero do filme (comportamento garantido pelo v-for). Além disso, adicionamos um listener ao evento click da tag span que chama o método listMovies passando o id do gênero como parâmetro. Por fim, adicionamos o conteúdo {{ getGenreById(genre_id) }} que exibe o nome do gênero a partir do id do gênero.

Por fim, vamos fazer uma estilização para a exibição dos gêneros. Para tal, vamos editar o bloco style do arquivo MoviesView.vue e adicionar o seguinte conteúdo:

.movie-genres {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: center;
  gap: 0.2rem;
}

.movie-genres span {
  background-color: #748708;
  border-radius: 0.5rem;
  padding: 0.2rem 0.5rem;
  color: #fff;
  font-size: 0.8rem;
  font-weight: bold;
}

.movie-genres span:hover {
  cursor: pointer;
  background-color: #455a08;
  box-shadow: 0 0 0.5rem #748708;
}

Note que a classe .movie-genres já existia no bloco style do arquivo MoviesView.vue. Neste caso, apenas alteramos o valor do atributo display para flex e adicionamos os demais atributos para que os gêneros sejam exibidos em uma única linha, com um pequeno espaçamento entre eles.

Formatando a data para o padrão brasileiro

Neste exemplo, vamos alterar a forma como a data de lançamento dos filmes é exibida. Para isso, vamos abrir o arquivo MoviesView.vue e alterar o seguinte conteúdo no bloco template:

<p class="movie-release-date">{{ formatDate(movie.release_date) }}</p>

Note que localizamos a tag p com a classe .movie-release-date e substituímos o conteúdo {{ movie.release_date }} pelo conteúdo {{ formatDate(movie.release_date) }}. Além disso, vamos adicionar o seguinte conteúdo no bloco script:

const formatDate = (date) => new Date(date).toLocaleDateString('pt-BR');

Note que criamos uma função formatDate que recebe a data de lançamento como parâmetro e retorna a data formatada para o padrão brasileiro. Para isso, usamos a função toLocaleDateString do JavaScript para formatar a data para o padrão brasileiro. Sugiro colocar a função formatDate logo após a função getGenreName.

Gerenciamento de estados

Nas aplicações web, é comum que tenhamos que lidar com estados. Por exemplo, em uma aplicação de cadastro de clientes, podemos ter um estado que indica se o cliente está sendo editado ou não. Em uma aplicação de carrinho de compras, podemos ter um estado que indica se o carrinho está vazio ou não. Em uma aplicação de listagem de filmes, podemos ter um estado que indica se os filmes estão sendo carregados ou não, ou mesmo armazenar os filmes ou gêneros que foram carregados.

No VueJS, podemos usar a API de reatividade para gerenciar estados. Contudo, essa abordagem pode se tornar complexa conforme a aplicação cresce. Para facilitar o gerenciamento de estados, podemos usar uma biblioteca chamada Pinia, como já estudamos anteriormente.

Instalação

Para instalar o Pinia, vamos abrir o terminal e executar o seguinte comando:

npm install pinia

Configuração da aplicação para suportar o Pinia

Vamos editar o arquivo src/main.js e adicionar o seguinte código:

import 'vue-loading-overlay/dist/css/index.css';
import './assets/main.css';

import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';
import router from './router';
const app = createApp(App);
app.use(router);
app.use(createPinia());

app.mount('#app');

Note que, comparado com o arquivo src/main.js anterior, adicionamos apenas a importação do createPinia e a chamada do método app.use(createPinia()). O método createPinia cria uma instância do Pinia e a torna disponível para todos os componentes da aplicação.

Criando uma store

Podemos dividir o processo de criação de uma store em três etapas:

  1. Criar a store usando o método defineStore
  2. Criar as variáveis reativas e funções que alteram essas variáveis
  3. Retornar as variáveis e funções que serão usadas pelos componentes

Para criar uma store, vamos criar um arquivo chamado src/stores/genre.js e adicionar o seguinte código:

import { reactive, computed } from 'vue';
import { defineStore } from 'pinia';
import api from '@/plugins/axios';

export const useGenreStore = defineStore('genre', () => {
  const state = reactive({
    genres: [],
  });

  const genres = computed(() => state.genres);
  const getGenreName = (id) =>
    state.genres.find((genre) => genre.id === id).name;

  const getAllGenres = async (type) => {
    const response = await api.get(`genre/${type}/list?language=pt-BR`);
    state.genres = response.data.genres;
  };

  return { genres, getAllGenres, getGenreName };
});

Note que, para criar a store, usamos o método defineStore que recebe dois parâmetros: o nome da store e uma função que retorna um objeto com as variáveis reativas e funções que alteram essas variáveis.

No nosso exemplo, criamos uma store chamada genre que possui uma variável reativa chamada genres e três funções: getAllGenres, getGenreName e getGenreName. A função getAllGenres faz uma requisição HTTP para a API do TMDB e armazena os gêneros na variável reativa genres. Esta função recebe um parâmetro chamado type que indica se a listagem de gêneros será feita com base nos filmes ou nas séries. A função getGenreName recebe o id de um gênero e retorna o nome desse gênero.

Note também que a função defineStore retorna um objeto com a propriedade computada genres e as funções getAllGenres e getGenreName. No exemplo não retornamos o objeto reativo state porque não queremos que ele seja acessado diretamente pelos componentes. Para acessar cada um dos elementos do objeto state, vamos usar os getters genres e getGenreName. Da mesma forma, para alterar o estado da aplicação, vamos usar a action getAllGenres.

Por fim, exportamos a store usando o método useGenreStore. Esse método é necessário para que o Pinia possa criar uma instância da store e torná-la disponível para os componentes da aplicação.

Usando a store

Para usar a store, vamos editar o arquivo src/views/MoviesView.vue. Inicialmente vamos editar o bloco script e adicionar o seguinte código:

import { useGenreStore } from '@/stores/genre';

const genreStore = useGenreStore();

Note que importamos a store usando o método useGenreStore que criamos anteriormente. No mesmo bloco script vamos alterar o método onMounted para que ele chame a função getAllGenres da store:

onMounted(async () => {
  isLoading.value = true;
  await genreStore.getAllGenres('movie');
  isLoading.value = false;
});

No exemplo, ao invocar a função getAllGenres, passamos o parâmetro 'movie' para que a listagem de gêneros seja feita com base nos filmes.

Por fim, vamos alterar o bloco template para que ele use a store:

<li
  v-for="genre in genreStore.genres"
  :key="genre.id"
  @click="listMovies(genre.id)"
  class="genre-item"
>
  {{ genre.name }}
</li>

No exemplo, alteramos apenas a listagem de gêneros, visto que foi o único elemento que usamos da store, e esta é a única store que criamos até o momento. Note que, para acessar a variável reativa genres da store, usamos o getter genres.

Ainda, vamos fazer mais uma alteração no bloco template, para buscar o nome do gênero com base no id do gênero:

<span
  v-for="genre_id in movie.genre_ids"
  :key="genre_id"
  @click="listMovies(genre_id)"
>
  {{ genreStore.getGenreName(genre_id) }}
</span>

Nesse exemplo, usamos a função getGenreName para buscar o nome do gênero com base no id do gênero. Essa função recebe o id do gênero e retorna o nome do gênero e está implementada na store genre.

Exercícios

  1. Fazer as alterações necessárias em TvView.vue para que a listagem de gêneros seja feita usando a store genre. Note que ao chamar a função getAllGenres da store genre, é necessário passar o parâmetro 'tv' para que a listagem de gêneros seja feita com base nos filmes.

Enfatizando o gênero atual

Nessa seção, vamos aprender a enfatizar o gênero atual selecionado. Para isso, vamos usar o store de gêneros que criamos anteriormente e vamos adicionar uma classe CSS para enfatizar o gênero atual.

Alterando o store de gêneros

Para poder enfatizar o gênero atual que está selecionado, vamos inicialmente editar o store que manipula os dados de gênero, no arquivo @/store/genre.js. Para isso, vamos adicionar uma nova propriedade chamada currentGenre que vai armazenar o gênero atual selecionado e as funções para manipular essa propriedade.

Inicialmente, vamos substituir a definição de estado pela seguinte:

const state = reactive({
  genres: [],
  currentGenreId: null,
});

Note que agora temos uma propriedade chamada currentGenreId que vai armazenar o id do gênero atual selecionado. Em seguida, vamos adicionar o seguinte getter para ler o valor dessa propriedade:

const currentGenreId = computed(() => state.currentGenreId);

Nesse caso, estamos usando um getter computado, pois queremos que o valor seja atualizado automaticamente quando a propriedade currentGenreId for alterada. Por fim, faremos uma action para alterar o valor dessa propriedade:

const setCurrentGenreId = (genreId) => {
  state.currentGenreId = genreId;
};

Nesse exemplo, estamos usando uma função para alterar o valor da propriedade currentGenreId para o valor passado como parâmetro. Agora, basta substituir o retorno da função defineStore pelo seguinte:

return {
  genres,
  getAllGenres,
  getGenreName,
  currentGenreId,
  setCurrentGenreId,
};

Note que estamos retornando a propriedade computada currentGenreId e a função setCurrentGenreId.

Alterando o componente MoviesView.vue

Inicialmente, vamos alterar a função listMovies para que ela altere o valor da propriedade currentGenreId da store de gêneros. Para isso, vamos adicionar o seguinte código no bloco script:

const listMovies = async (genreId) => {
  genreStore.setCurrentGenreId(genreId);
  isLoading.value = true;
  const response = await api.get('discover/movie', {
    params: {
      with_genres: genreId,
      language: 'pt-BR',
    },
  });
  movies.value = response.data.results;
  isLoading.value = false;
};

Note que, antes de fazer a requisição para a API, chamamos a função setCurrentGenreId da store de gêneros passando o id do gênero selecionado como parâmetro. Em seguida, mantemos o restante do código inalterado.

Agora, vamos alterar o bloco template para que ele use a propriedade currentGenreId da store de gêneros. Para isso, vamos alterar os seguintes códigos no bloco template:

  <li
    v-for="genre in genreStore.genres"
    :key="genre.id"
    @click="listMovies(genre.id)
    class="genre-item"
    :class="{ active: genre.id === genreStore.currentGenreId }"
  >
    {{ genre.name }}
  </li>

e também

<span
  v-for="genre_id in movie.genre_ids"
  :key="genre_id"
  @click="listMovies(genre_id)"
  :class="{ active: genre_id === genreStore.currentGenreId }"
>
  {{ genreStore.getGenreName(genre_id) }}
</span>

Por fim, vamos adicionar a seguinte classe CSS no bloco style:

.active {
  background-color: #67b086;
  font-weight: bolder;
}

.movie-genres span.active {
  background-color: #abc322;
  color: #000;
  font-weight: bolder;
}

Mostrando detalhes do filme

Vamos agora, possibilitar a exibição de detalhes de um filme ao clicar em um cartão de filme.

Criando um store

Vamos criar um arquivo chamado src/stores/movie.js e adicionar o seguinte código:

import { reactive, computed } from 'vue';
import { defineStore } from 'pinia';
import api from '@/plugins/axios';

export const useMovieStore = defineStore('movie', () => {
  const state = reactive({
    currentMovie: {},
  });

  const currentMovie = computed(() => state.currentMovie);

  const getMovieDetail = async (movieId) => {
    const response = await api.get(`movie/${movieId}`);
    state.currentMovie = response.data;
  };

  return { currentMovie, getMovieDetail };
});

Note que estamos criando uma store chamada movie e estamos retornando a propriedade computada currentMovie e a função getMovieDetail. A propriedade currentMovie vai armazenar o filme atual selecionado e a função getMovieDetail vai buscar os detalhes do filme na API e armazenar na propriedade currentMovie.

Também, existe uma integração com a store de template para que seja possível mostrar o ícone de carregando enquanto os detalhes do filme são buscados na API.

Alterando o componente MoviesView.vue

Vamos aquivo editar o arquivo src/views/MoviesView.vue, procurar para tag img com a capa do filme e adicionar um listener para o evento click que vai chamar a função showMovieDetails passando o id do filme como parâmetro. O código deve ficar assim:

<img
  :src="`https://image.tmdb.org/t/p/w500${movie.poster_path}`"
  :alt="movie.title"
  @click="openMovie(movie.id)"
/>

Agora, vamos editar o bloco script e adicionar o seguinte código:

import { useRouter } from 'vue-router'
const router = useRouter()

...

function openMovie(movieId) {
  router.push({ name: 'MovieDetails', params: { movieId } });
}

Note que estamos importando o hook useRouter do vue-router e estamos criando uma função chamada openMovie que recebe o id do filme como parâmetro. Essa função vai redirecionar o usuário para a rota MovieDetails passando o id do filme como parâmetro.

Criando o componente MovieDetailsView.vue

Vamos criar um novo componente chamado MovieDetailsView.vue, na pasta src/views/ e vamos adicionar o seguinte código:

<script setup>
  import { defineProps, onMounted } from 'vue';
  import { useMovieStore } from '@/stores/movie';
  const movieStore = useMovieStore();

  const props = defineProps({
    movieId: {
      type: Number,
      required: true,
    },
  });

  onMounted(async () => {
    await movieStore.getMovieDetail(props.movieId);
  });
</script>

<template>
  <div class="main">
    <div class="content">
      <img
        :src="`https://image.tmdb.org/t/p/w185${movieStore.currentMovie.poster_path}`"
        :alt="movieStore.currentMovie.title"
      />

      <div class="details">
        <h1>Filme: {{ movieStore.currentMovie.title }}</h1>
        <p>{{ movieStore.currentMovie.tagline }}</p>
        <p>{{ movieStore.currentMovie.overview }}</p>
        <p>Orçamento: ${{ movieStore.currentMovie.budget }}</p>
        <p>Avaliação: {{ movieStore.currentMovie.vote_average }}</p>
      </div>
    </div>
  </div>

  <p>Produtoras</p>
  <div class="companies">
    <template
      v-for="company in movieStore.currentMovie.production_companies"
      :key="company.id"
    >
      <img
        v-if="company.logo_path"
        :src="`https://image.tmdb.org/t/p/w92${company.logo_path}`"
        :alt="company.name"
      />
      <p v-else>{{ company.name }}</p>
    </template>
  </div>
</template>

<style scoped>
  .companies {
    display: flex;
    flex-direction: row;
    column-gap: 3rem;
    align-items: center;
    margin-bottom: 2rem;
  }
</style>

Esse código é um pouco mais complexo, mas não se preocupe, vamos explicar cada parte dele.

Inicialmente, importamos o hook useMovieStore da store movie e criamos uma variável reativa chamada movieStore que vai armazenar a instância da store movie.

Em seguida, criamos uma propriedade chamada movieId que vai receber o id do filme como parâmetro. Essa propriedade é obrigatória e deve ser do tipo Number.

Também, criamos um hook onMounted que vai chamar a função getMovieDetail da store movie passando o id do filme como parâmetro. Essa função vai buscar os detalhes do filme na API e armazenar na propriedade currentMovie da store movie.

Por fim, no bloco template, usamos a propriedade computada currentMovie da store movie para mostrar os detalhes do filme. Note que, para acessar a propriedade computada currentMovie da store movie, usamos a variável reativa movieStore.

Nesse exemplo, não foram realizadas muitas estilizações em CSS, mas você pode fazer isso se quiser.

Alterando as rotas

Vamos editar o arquivo src/router/index.js e adicionar a seguinte rota:

{
  path: '/movie/:movieId',
  name: 'MovieDetails',
  component: () => import('../views/MovieDetailsView.vue'),
  props: true,
},