Tutorial: Costruire un’app per la prenotazione di un museo con Next.js e l’API di Timerise

Questo tutorial vi guida nella creazione di un’applicazione per la prenotazione di musei utilizzando Next.js, TypeScript, Timerise API, Apollo Client e Tailwind CSS. Ci concentreremo sull’integrazione dell’API headless di Timerise, dimostrando i vantaggi di questa soluzione.

Prerequisiti

  • Conoscenza di base di TypeScript e React
  • Node.js installato
  • Accesso all’API di Timerise

Passo 1: Impostazione del progetto Next.js

Il primo passo per costruire la nostra applicazione di prenotazione museale è la creazione di un nuovo progetto Next.js. Next.js introduce un processo di configurazione migliorato, che consente di configurare fin dall’inizio funzionalità essenziali come Tailwind CSS, ESLint e altro ancora. Questo approccio semplificato semplifica l’impostazione iniziale del progetto, consentendoci di concentrarci sulla realizzazione delle funzionalità principali dell’app.

Creare una nuova applicazione Next.js
Eseguire il comando per avviare la creazione di una nuova applicazione Next.js. Vi verrà richiesto di scegliere varie opzioni di configurazione, tra cui l’integrazione di Tailwind CSS, che useremo per lo stile della nostra applicazione.

Bash
npx create-next-app@latest museum-booking-app --typescript
cd museum-booking-app

Quando viene richiesto, scegliere le configurazioni preferite:

  • ESLint: Scegliere‘Sì‘ per i controlli di qualità del codice.
  • Tailwind CSS: Selezionare“Sì” per impostare automaticamente Tailwind CSS.
  • Usa la cartella src/: Scegliere‘Sì‘ per una struttura del progetto più pulita.
  • App Router: Selezionare“Sì” per utilizzare la nuova funzione App Router.
  • Personalizza alias di importazione predefinito: selezionare‘Sì‘ se si desidera personalizzare i percorsi di importazione.

Passo 2: Installazione delle dipendenze

Con la configurazione migliorata di Next.js, le dipendenze chiave come Tailwind CSS sono già integrate. Tuttavia, dobbiamo ancora installare altri pacchetti che sono fondamentali per la nostra applicazione, in particolare Apollo Client per gestire le query GraphQL e le mutazioni con l’API di Timerise.

Installare il client Apollo
Apollo Client è una libreria completa per la gestione degli stati in JavaScript che consente di gestire dati locali e remoti con GraphQL. Questo sarà il nostro strumento principale per interagire con l’API di Timerise.

Bash
npm install @apollo/client graphql

Verifica della configurazione CSS di Tailwind (opzionale)
Poiché il CSS di Tailwind è stato impostato durante l’inizializzazione del progetto, verificare i file di configurazione (tailwind.config.js e postcss.config.js) per assicurarsi che soddisfino le esigenze di stile del progetto. È possibile effettuare le regolazioni necessarie per personalizzare l’impostazione di Tailwind.

Dipendenze aggiuntive
A seconda dei requisiti del progetto, potrebbero essere necessarie altre dipendenze. Ad esempio, se si prevede di gestire date e orari (cosa comune in un’applicazione di prenotazione), si deve considerare l’installazione di date-fn:

Bash
npm install date-fns

Passo 3: Configurazione del client Apollo

Ora che il nostro progetto è impostato con le dipendenze necessarie, è il momento di configurare Apollo Client. Apollo Client è uno strumento potente e flessibile per la gestione dei dati locali e remoti nelle applicazioni JavaScript. Semplifica il processo di interazione con le API GraphQL, come quella di Timerise che stiamo utilizzando. Impostando Apollo Client, consentiamo alla nostra applicazione di eseguire in modo efficiente le query e le mutazioni, di gestire gli stati di caricamento, di memorizzare i dati nella cache e di gestire gli errori, aspetti essenziali dell’interazione con il nostro sistema di prenotazione.

Impostazione del client Apollo in lib/apolloClient.ts

Dobbiamo creare un’istanza di Apollo Client che punti al nostro endpoint GraphQL API. Questa istanza sarà utilizzata in tutta l’applicazione per interagire con l’API di Timerise.

TypeScript
import { ApolloClient, InMemoryCache } from '@apollo/client';

const apolloClient = new ApolloClient({
  uri: 'https://sandbox-api.timerise.io/v1',
  cache: new InMemoryCache(),
});

export default apolloClient;
  • Il uri è impostato sull’endpoint dell’API di Timerise.
  • InMemoryCache è usato per memorizzare nella cache i risultati delle query dopo averli recuperati. Ciò contribuisce a ottimizzare le prestazioni della nostra applicazione, riducendo il numero di chiamate API necessarie.

Questa configurazione è un passo fondamentale per consentire alla nostra applicazione di comunicare efficacemente con l’API di Timerise. Questo pone le basi per le fasi successive, in cui creeremo ed eseguiremo le query GraphQL e le mutazioni per gestire le prenotazioni.

Avvolgere l’applicazione con ApolloProvider
Nel file app/page.tsx, importare ApolloProvider da @apollo/client e l’istanza del client Apollo. Quindi, avvolgere il componente principale dell’applicazione e passargli l’istanza del client.

TypeScript
'use client'
import { ApolloProvider } from '@apollo/client';
import apolloClient from '../lib/apolloClient';
import SlotSelection from "@/components/SlotSelection";

export default function Home() {
  return (
    <ApolloProvider client={apolloClient}>
      <SlotSelection serviceId="YourServiceId" />
    </ApolloProvider>
  );
};

Passo 4: Creare query e mutazioni GraphQL

Le query GraphQL ci permettono di recuperare i dati e le mutazioni ci permettono di modificarli. Qui definiamo le query GraphQL e le mutazioni necessarie per interagire con l’API di Timerise per ottenere servizi e prenotare slot.

Definire le operazioni GraphQL in graphql/queries.ts

TypeScript
import { gql } from '@apollo/client';

export const SERVICE_QUERY = gql`
  query Service($serviceId: ID!, $slotType: SlotType!) {
    service(serviceId: $serviceId) {
      serviceId
      title
      description
      media {
        title
        url
      }
      slots(slotType: $slotType) {
        slotId
        dateTimeFrom
        dateTimeTo
        duration
        quantity
      }
    }
  }
`;

export const BOOKING_QUERY = gql`
  query Booking($bookingId: ID!) {
    booking(bookingId: $bookingId) {
      bookingId
      shortId
      shortUrl
      qrUrl
      status
    }
  }
`;

Definire la mutazione in graphql/mutations.ts

TypeScript
import { gql } from '@apollo/client';

export const BOOKING_CREATE_MUTATION = gql`
  mutation BookingCreate($serviceId: ID!, $slots: [ID]) {
    bookingCreate(serviceId: $serviceId, slots: $slots) {
      bookingId
      qrUrl
      shortUrl
      status
    }
  }
`;

Fase 5: Creazione dei componenti dell’applicazione

Definite le interazioni con le API, ci concentriamo ora sulla costruzione dei componenti React. Questi componenti forniranno l’interfaccia per selezionare gli slot e confermare le prenotazioni, utilizzando le query e le mutazioni che abbiamo impostato.

Componente di selezione degli slot (SlotSelection.tsx)
Codice e spiegazione per la creazione di un componente per la selezione degli slot disponibili.

TypeScript
import React, { useState } from 'react';
import { useRouter } from 'next/navigation'
import { useQuery, useMutation } from '@apollo/client';
import { SERVICE_QUERY } from '../graphql/queries';
import { BOOKING_CREATE_MUTATION } from '../graphql/mutations';

const SlotSelection = ({ serviceId }: { serviceId: string }) => {
  const router = useRouter();
  const [selectedSlot, setSelectedSlot] = useState<string|null>(null);
  const { loading, error, data } = useQuery(SERVICE_QUERY, {
    variables: { serviceId: serviceId, slotType: "AVAILABLE" },
  });
  const [createBooking, { data: bookingData, loading: bookingLoading, error: bookingError }] = useMutation(BOOKING_CREATE_MUTATION);

  if (loading || bookingLoading) return <p>Loading...</p>;
  if (error || bookingError) return <p>Error loading.</p>;

  const handleSlotSelect = async (slotId: string) => {
    setSelectedSlot(slotId);
    try {
      const { data } = await createBooking({
        variables: { serviceId, slots: [slotId] }
      });
      if (data.bookingCreate && data.bookingCreate.bookingId) {
        router.push(`/confirmation/${data.bookingCreate.bookingId}`);
      }
    } catch (error) {
      console.error("Error creating booking:", error);
      // Handle booking error
    }
  };

  return (
    <div className="p-4">
      <h2 className="text-lg font-semibold">Select a Slot</h2>
      <ul>
        {data.service.slots.map((slot: { slotId: string, dateTimeFrom: Date }) => (
          <li key={slot.slotId} className="my-2">
            <button
              className={`p-2 border ${selectedSlot === slot.slotId ? 'border-blue-500' : 'border-gray-300'}`}
              onClick={() => handleSlotSelect(slot.slotId)}>
              {new Date(slot.dateTimeFrom).toLocaleString()}
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default SlotSelection;

Componente di conferma (ConfirmationView.tsx)
Codice e spiegazione per la creazione di un componente che visualizza i dettagli della conferma della prenotazione, compreso il codice QR.

TypeScript
import React from 'react';
import { useQuery } from '@apollo/client';
import { BOOKING_QUERY } from '@/graphql/queries';

const ConfirmationView = ({ bookingId }: { bookingId: string }) => {
  const { loading, error, data } = useQuery(BOOKING_QUERY, {
    variables: { bookingId: bookingId },
  });

  if (loading) return <p>Loading booking...</p>;
  if (error) return <p>Error loading booking.</p>;
  return (
    <div className="p-4">
      <h2 className="text-lg font-semibold">Booking status: {data.booking.status}</h2>
      <p className="my-2">Booking ID: {data.booking.shortId}</p>
      <div className="my-2">
        <img src={data.booking.qrUrl} alt="QR Code" className="w-32 h-32" />
      </div>
      <a href={data.booking.shortUrl} target="_blank" className="text-blue-500">View booking page</a>
    </div>
  );
};

export default ConfirmationView;

Passo 6: Sviluppo front-end con Tailwind CSS

Questo passo è dedicato allo stile dell’applicazione. Tailwind CSS, un framework CSS utility-first, sarà utilizzato per progettare un’interfaccia utente pulita e reattiva. Questo approccio ci permette di costruire un front-end visivamente accattivante in modo rapido ed efficiente, migliorando l’esperienza dell’utente.

In SlotSelection.tsx, applicheremo le classi Tailwind per il layout, la spaziatura e l’estetica.

TypeScript
return (
  <div className="p-4 max-w-md mx-auto">
    <h2 className="text-2xl font-bold text-center text-white-800 mb-4">Select a Slot</h2>
    <ul className="list-none space-y-3">
        {data.service.slots.map((slot: { slotId: string, dateTimeFrom: Date }) => (
          <li key={slot.slotId} className="flex justify-between items-center p-3 border rounded-lg shadow-sm">
            <span className="text-white-600">{new Date(slot.dateTimeFrom).toLocaleString()}</span>
            <button
              className={`px-4 py-2 rounded-lg text-black ${selectedSlot === slot.slotId ? 'bg-blue-500' : 'bg-gray-300'}`}
              onClick={() => handleSlotSelect(slot.slotId)}>
              Select
            </button>
          </li>
        ))}
    </ul>
  </div>
);

In ConfirmationView.tsx, stilizzare i dettagli della conferma della prenotazione.

TypeScript
return (
  <div className="p-4 max-w-md mx-auto">
    <h2 className="text-2xl font-bold text-center text-white-800 mb-4">BOOKING {data.booking.status}</h2>
    <div className="bg-white shadow overflow-hidden sm:rounded-lg p-4">
      <p className="text-center text-gray-600 mb-2">ID: <span className="text-gray-800 font-semibold">{data.booking.shortId}</span></p>
      <div className="text-center">
        <img src={data.booking.qrUrl} alt="QR Code" className="w-32 h-32 inline-block mb-3" />
        <p className="text-sm text-gray-600">Scan this QR code at the entrance</p>
      </div>
      <a href={data.booking.shortUrl} target="_blank" rel="noopener noreferrer" className="text-blue-500 hover:text-blue-700 text-center block mt-4">
        View booking page
      </a>
    </div>
  </div>
);

Passo 8: Distribuzione con Vercel

Il deploy della nostra applicazione la rende accessibile agli utenti sul web. Vercel, armonizzandosi bene con Next.js, offre un’esperienza di distribuzione senza soluzione di continuità. Automatizza il processo dal push del codice alla produzione, gestendo le funzioni serverless, il servizio di file statici e altro ancora, garantendo prestazioni e scalabilità ottimali.

Spingere il codice su GitHub
Prima di distribuire, assicurarsi che il progetto sia stato inviato a un repository GitHub:

Bash
git init
git add .
git commit -m "Init commit"
git branch -M main
git remote add origin https://github.com/your-username/your-repo-name.git
git push -u origin main

Impostazione della distribuzione su Vercel

  1. Accedere a Vercel e collegare il proprio account GitHub.
  2. Scegliere il repository appena spinto.
  3. Vercel rileva automaticamente che si tratta di un’applicazione Next.js e suggerisce le impostazioni di compilazione. Accettatele o modificatele secondo le necessità.
  4. Fare clic su“Distribuisci” per avviare il processo di distribuzione. Vercel gestisce la creazione e la distribuzione, fornendo un URL attivo.

Vercel offre funzioni come l’HTTPS automatico, il supporto di domini personalizzati e l’analisi in tempo reale. È anche possibile impostare le variabili d’ambiente e gestire altre impostazioni direttamente dalla dashboard di Vercel.

Accesso al codice completo dell’applicazione

Per completare questo tutorial e migliorare la vostra esperienza di apprendimento, abbiamo reso disponibile su GitHub il codice sorgente completo dell’applicazione per la prenotazione dei musei. Questa è una grande opportunità per esplorare la base di codice, sperimentare modifiche e comprendere la struttura e la funzionalità dell’applicazione in modo più dettagliato.

Accesso al repository
L’intero codice sorgente dell’applicazione di prenotazione del museo è ospitato su GitHub. È possibile accedervi al seguente URL: https://github.com/timerise-io/museum-booking-app

Clonare il repository
Per lavorare con il codice sulla propria macchina locale, clonare il repository usando Git:

Bash
git clone https://github.com/timerise-io/museum-booking-app.git
cd museum-booking-app

Esplorare e modificare
Una volta ottenuto il codice, sentitevi liberi di esplorarlo e modificarlo. Se si desidera comprendere funzionalità specifiche, aggiungere nuove caratteristiche o personalizzare l’interfaccia utente, questa base di codice può servire come pratico punto di partenza.

Visita la demo
È possibile accedere alla demo live dell’applicazione di prenotazione dei musei al seguente URL: https://museum-booking-app.vercel.app/

Conclusione

Questo tutorial ha fornito una guida passo passo per impostare un’applicazione Next.js, integrarla con un’API headless come Timerise e distribuirla. Gli snippet di codice sono parti cruciali dell’applicazione e dimostrano come integrare l’API di Timerise con un’applicazione Next.js utilizzando Apollo Client for GraphQL.