Build a Self-care Application Using Next.js and Appwrite

Build a Self-care Application Using Next.js and Appwrite

Part 2: Creating Calming Sounds in Our Appwrite and Next.js App

Introduction

In this tutorial series, we are exploring how to build a self-care app using Next.js, a popular React framework, and Appwrite, an open-source backend platform. In Part 1, we laid the foundation by setting up the project, implementing user authentication, and creating basic self-care components.

In Part 2, we will delve deeper into the development process by focusing on a new and exciting feature: Calming Sounds. We all know that soothing sounds can significantly contribute to relaxation and stress reduction. By leveraging Next.js, we will enhance our self-care app by integrating a feature that allows users to explore and play a collection of calming sounds.

Adding Our Sound Files And Creating CalmSounds Component

Let's start by creating public/sounds folder and add our mp3 sound files to the folder.

Then in components/Calmsounds.js add the following code:

import React, { useState } from 'react';
import Link from 'next/link';

const CalmSounds = () => {
  const [sounds, setSounds] = useState([
    { id: 1, name: 'FirePlace', source: '/sounds/fireplace.mp3' },
    { id: 2, name: 'Crickets', source: '/sounds/crickets.mp3' },
    { id: 3, name: 'Bird Chirping', source: '/sounds/junglebirds.mp3' },
  ]);

  const handlePlaySound = (id) => {
    const updatedSounds = sounds.map((sound) => {
      if (sound.id === id) {
        return { ...sound, playing: true };
      } else {
        return { ...sound, playing: false };
      }
    });
    setSounds(updatedSounds);
  };

  return (
    <div className="calm-sounds">
      <h2 className="component-title">Calm Sounds</h2>
      <div className="sounds-container">
        {sounds.map((sound) => (
          <div
            key={sound.id}
            className={`sound ${sound.playing ? 'playing' : ''}`}
          >
            <div className="sound-name">{sound.name}</div>
            <audio
              src={sound.source}
              controls={sound.playing}
              autoPlay={sound.playing}
              loop
            />
            <button onClick={() => handlePlaySound(sound.id)}>Play</button>
          </div>
        ))}
      </div>
      <div className="buttons-container">
        <Link href="/Sounds">
          <button>Explore More</button>
        </Link>
      </div>
    </div>
  );
};

export default CalmSounds;

In the CalmSounds component:

  1. The useState hook is used to define and initialize the sounds state variable. It is an array of objects, where each object represents a sound with properties like id, name, and source. The initial state includes three sounds.

  2. The handlePlaySound function is responsible for updating the state when a sound is played. It maps over the sounds array and sets the playing property to true for the selected sound and false for others. This ensures only one sound is played at a time.

  3. The return statement contains the JSX code that defines the component's structure and rendering logic.

    • The component has a <div> container with the class name "calm-sounds".

    • Inside the container, there's an <h2> element with the class name "component-title" displaying the title "Calm Sounds".

    • Next, there's a <div> container with the class name "sounds-container" that holds the list of sounds.

    • Inside the sounds container, the sounds array is mapped using the map function to render each sound as a <div> element with the class name "sound". The sound.playing class is conditionally added to highlight the currently playing sound.

    • Each sound <div> contains the sound's name, an <audio> element with the src attribute pointing to the sound's source file, and play controls like controls, autoPlay, and loop.

    • Additionally, there's a "Play" button for each sound that triggers the handlePlaySound function when clicked, passing the sound's id as an argument.

    • After the sounds container, there's another <div> container with the class name "buttons-container".

    • Inside this container, there's a <Link> component from Next.js that wraps a "Explore More" button. Clicking the button will navigate to the "/Sounds" route.

  4. Finally, the component is exported as the default export.

Export the Component to the Homepage

In the index.js import our CalmSounds.js component then render it within Home parent component:

return (
    <>
        <Header />
        <EncouragingQuotes />
        <SelfCareList selfCareItems={selfCareItems} />
        <CalmSounds />

Your homepage should now have this output:

Optimize the Caching of the Sound Files

To optimize the caching of the sound files in our self-care app, we can use the nextConfig object and the headers function provided by Next.js. By setting appropriate cache control headers, we can ensure that the sound files are cached by the user's browser, resulting in faster subsequent requests and improved performance.

To add this configuration to our Next.js project, add the following code into the next.config.js file:

const nextConfig = {
    async headers() {
      return [
        {
          source: '/sounds/(.*)',
          headers: [
            {
              key: 'Cache-Control',
              value: 'public, max-age=31536000, immutable',
            },
          ],
        },
      ];
    },
  };

  module.exports = nextConfig;

This code sets the cache-control header for any request to the /sounds/ route and its subpaths. The max-age directive is set to one year (31536000 seconds), indicating that the sound files can be cached by the browser for a long duration. The immutable directive ensures that the cached files are not revalidated with the server until the specified max-age expires.

Creating Sounds.js File

Let's now create a sounds.js file to add the rest of our sounds:

import React, { useState } from 'react';
import Header from './components/Header'

const CalmSounds = () => {
  const [sounds, setSounds] = useState([
    { id: 1, name: 'Calm River', source: '/sounds/calm-river.mp3' },
    { id: 2, name: 'Forest', source: '/sounds/forest.mp3' },
    { id: 3, name: 'Bird Chirping', source: '/sounds/evening-birds.mp3' },
    { id: 4, name: 'Light Rain', source: '/sounds/light-rain.mp3' },
    { id: 5, name: 'Minor Arp', source: '/sounds/minor-arp.mp3' },
    { id: 6, name: 'Relaxing', source: '/sounds/relaxing.mp3' },
    { id: 7, name: 'Soft-rain', source: '/sounds/soft-rain.mp3' },
    { id: 8, name: 'Super Spacy', source: '/sounds/superspacy.mp3' },
    { id: 9, name: 'Piano', source: '/sounds/the-last-piano.mp3' },
    { id: 10, name: 'Uplifting', source: '/sounds/uplifting-pad.mp3' },
    { id: 11, name: 'Wind Chimes', source: '/sounds/wind-chimes.mp3' },
    { id: 12, name: 'Chicken Sounds', source: '/sounds/chicken-sounds.mp3' },

  ]);

  const handlePlaySound = (id) => {
    const updatedSounds = sounds.map((sound) =>
      sound.id === id ? { ...sound, playing: true } : { ...sound, playing: false }
    );
    setSounds(updatedSounds);
  };

  return (
    <div>
      <Header />
      {sounds.map((sound) => (
        <div key={sound.id} className={`sound ${sound.playing ? 'playing' : ''}`}>
          <div className="sound-name">{sound.name}</div>
          <audio src={sound.source} controls={sound.playing} autoPlay={sound.playing} loop />
          <button onClick={() => handlePlaySound(sound.id)}>Play</button>
        </div>
      ))}
    </div>
  );
};

export default CalmSounds;

The CalmSounds component renders a list of sound items with play buttons and audio controls. Here's an explanation of the code:

It includes the Header component being imported and rendered. Additionally, the sounds array is expanded with more sound objects. Here's a breakdown:

  1. The Header component is imported from the './components/Header' file.

  2. The sounds array is expanded with additional sound objects. Each sound object has properties such as id, name, and source, representing a unique identifier, the name of the sound, and the source URL of the sound file, respectively. The array now contains twelve sound objects with different names and sources.

  3. The handlePlaySound function remains the same and is responsible for updating the state when a sound is played.

  4. In the return statement, the Header component is added before the sounds.map function.

  5. Inside the sounds.map function, each sound is rendered as a <div> element with the class name "sound". The sound.playing class is conditionally added to highlight the currently playing sound.

  6. Each sound <div> includes the sound's name, an <audio> element with the src attribute pointing to the sound's source file, and play controls like controls, autoPlay, and loop.

  7. The "Play" button triggers the handlePlaySound function when clicked, passing the sound's id as an argument.

The CalmSounds component now renders the Header component and displays a list of sounds with play buttons. The sounds array is iterated over using the map function to render each sound element.

Adding the File to our Header

Now let's add our sound.js file in our Header component:

<li>
            <Link href="/Sounds" legacyBehavior>
              CalmSounds
            </Link>
          </li>

Finally, let's create a footer component. In components folder add a footer.js file and add the following code:

import React from 'react';

const Footer = () => {
  return (
    <footer className="footer">
      <div className="footer-content">
        <p>&copy; {new Date().getFullYear()} G-Care. All rights reserved.</p>
        <p>Terms of Service | Privacy Policy</p>
      </div>
    </footer>
  );
};

export default Footer;

Here's a breakdown of the code:

  1. The Footer component is defined as a functional component using the arrow function syntax.

  2. The component returns JSX code that represents the structure and content of the footer.

  3. The returned JSX code consists of a <footer> element with the class name "footer".

  4. Inside the <footer> element, there's a <div> element with the class name "footer-content".

  5. Within the <div> element, there are two <p> elements. The first <p> element displays the current year using JavaScript's new Date().getFullYear() function and a copyright notice. The second <p> element displays links for "Terms of Service" and "Privacy Policy".

  6. The content within the footer is static and doesn't include any dynamic behavior or interactions.

  7. Finally, the Footer component is exported as the default export.

This Footer component can be used in other parts of our application to render the footer section consistently by importing it and rendering it at the bottom of our code before our last div.

Conclusion

In this tutorial, we explored how to enhance our self-care app by adding a Calming Sounds feature using Next.js. We created a CalmSounds component that allows users to explore and play a collection of calming sounds.

To optimize the caching of the sound files, we utilized Next.js' nextConfig object and the headers function to set appropriate cache control headers. By doing so, we ensured that the sound files are cached by the user's browser, resulting in faster subsequent requests and improved performance.

We also created a sounds.js file to expand the list of available sounds and added the component to our Header for easy access. Additionally, we created a Footer component to provide a consistent footer section throughout our application.

By following this tutorial series, we have built a self-care app with features like user authentication, self-care components, and the ability to explore and play calming sounds. This app can serve as a valuable tool for users to prioritize their mental well-being and practice self-care.