Original article: https://www.freecodecamp.org/news/build-portfolio-website-react/

Hoy vas a crear una de las aplicaciones web más importantes que puedes construir: tu portafolio de desarrollador.

Todo desarrollador de React o desarrollador web en general necesita mostrar lo que puede hacer a potenciales clientes o empleadores.

Eso es exactamente lo que vamos a construir a continuación, con la ayuda de herramientas estándares de la industria como React, Tailwind CSS y Netlify.

¡Empecemos!

¿Cómo se verá el Portafolio?

portfolio-1-min

Esta es la versión final del portafolio que estaremos creando.

Incluirá información sobre ti, los proyectos que has hecho, las habilidades técnicas que has utilizado en esos proyectos, e información de contacto para que clientes o empleadores puedan comunicarse contigo.

¿Qué herramientas estaremos utilizando?

  • Utilizaremos React para crear la interfaz de usuario de la aplicación. Nos permitirá componer cada parte de nuestra página de inicio a través de componentes reutilizables y extender nuestra aplicación si quisiéramos agregar funciones adicionales, como por ejemplo un blog.
  • Para dar estilo a nuestra aplicación, utilizaremos Tailwind CSS. Para darle a nuestra aplicación una apariencia profesional, Tailwind nos permitirá aplicar fácilmente múltiples estilos mediante la combinación de nombres de clases de nuestros elementos de React.
  • Para desplegar nuestra aplicación en la web, utilizaremos el servicio gratuito de Netlify. Se podrá acceder a nuestro proyecto en un dominio personalizado a usuarios rápidamente a través de un CDN (Red de Distribución de contenido, por sus siglas en inglés).

Cómo empezar

Puedes descargar los archivos iniciales de nuestro proyecto aquí

Cuando tengas el código, todo lo que debes hacer es arrastrar la carpeta del proyecto (descomprimido) al editor de código y ejecutar el comando:

npm install

¡Y estás listo para empezar!

¿Qué herramientas necesito para construir mi Portafolio?

Para seguir el proceso entero de crear nuestra aplicación web desde cero al despliegue, necesitarás lo siguiente:

  1. Node.js instalado en tu computador. Puedes descargarlo en nodejs.org.
  2. Git instalado en tu computador. Puedes descargarlo en git-scm.com.
  3. Te recomendaría utilizar VS Code como editor de código. Puedes descargarlo en code.visualstudio.com.
  4. Una cuenta gratuita de Netlify en netlify.com.
  5. Una cuenta gratuita de Github en github.com.

Cómo construir la estructura del Portafolio

El beneficio de utilizar React es que podremos expandir nuestra aplicación a cuantas páginas queramos, de manera muy sencilla, y agregar mucho contenido adicional.

Sin embargo, dado que estamos trabajando únicamente con una página, podremos desde dentro del componente de la aplicación determinar los diferentes componentes necesarios muy rápidamente. Tendremos una Barra de Navegación (Navbar) en el nivel superior, con todos los enlaces que nos llevarán a las diferentes secciones del Portafolio.

Posteriormente, incluiremos las siguientes secciones: Acerca de mí (About),  Proyectos (Projects), Testimonios (Testimonials), y para finalizar un formulario de contacto (Contact).

Esta rápida planificación nos permitirá entender cómo debemos llamar a nuestros componentes y en qué orden. Podemos comenzar agregándolos todos en nuestro archivo App.js (en src):

// src/App.js

import React from "react";

export default function App() {
  return (
    <main>
      <Navbar />
      <About />
      <Projects />
      <Skills />
      <Testimonials />
      <Contact />
    </main>
  );
}

Cómo crear nuestros componentes

Ahora que tenemos una lista con todos estos componentes necesitaremos crearlos.

Dentro de nuestra carpeta src, crearemos una carpeta llamada componentes con todos los archivos que necesitaremos:

my-portfolio
├── README.md
├── node_modules
├── package.json
├── .gitignore
├── public
│   ├── favicon.ico
│   ├── index.html
│   └── manifest.json
└── src
    ├── App.js
    ├── data.js
    ├── index.css
    ├── index.js
    └── components
        ├── About.js
        ├── Contact.js
        ├── Navbar.js
        ├── Projects.js
        ├── Skills.js
        └── Testimonials.js

Luego crearemos la estructura básica de cada componente de React y lo exportaremos desde ese archivo con export default:

// src/components/About.js

export default function About() {}

// repeat the same basic structure for all 6 components

Y finalmente nos aseguraremos de importarlo en App.js:

// src/App.js

import React from "react";
import About from "./components/About";
import Contact from "./components/Contact";
import Navbar from "./components/Navbar";
import Projects from "./components/Projects";
import Skills from "./components/Skills";
import Testimonials from "./components/Testimonials";

export default function App() {
  return (
    <main>
      <Navbar />
      <About />
      <Projects />
      <Skills />
      <Testimonials />
      <Contact />
    </main>
  );
}

Notemos que debe haber seis componentes en total.

Introducción a Tailwind CSS

Una vez hayamos hecho eso, podemos empezar a trabajar con Tailwind CSS, para dar a nuestra aplicación una apariencia básica.

El beneficio de usar Tailwind CSS es que no tenemos que escribir ningún estilo manualmente en una página de CSS. Todo lo que debemos hacer es combinar múltiples clases para darle a la página la apariencia que queremos.

Por ejemplo, para dar a nuestro portafolio un fondo oscuro con texto gris aplicado a todos nuestros componentes hijos, puedes agregar las siguientes clases al elemento main:

// src/App.js

import React from "react";
import About from "./components/About";
import Contact from "./components/Contact";
import Navbar from "./components/Navbar";
import Projects from "./components/Projects";
import Skills from "./components/Skills";
import Testimonials from "./components/Testimonials";

export default function App() {
  return (
    <main className="text-gray-400 bg-gray-900 body-font">
      <Navbar />
      <About />
      <Projects />
      <Skills />
      <Testimonials />
      <Contact />
    </main>
  );
}

Cómo construir el componente About

Empecemos con nuestra primera sección, la sección "Acerca de mí". Esta consistirá de una introducción básica de nosotros y en cuáles habilidades técnicas nos especializamos.

También incluirá algunos enlaces al formulario de contacto así como a nuestros proyectos. Dado que estos enlaces serán a diferentes partes de la misma página, podremos utilizar las etiquetas: "/#projects" y "/#contact".

Para hacer que estos enlaces funcionen correctamente y así poder dirigirnos a cada sección, estableceremos el atributo id de la sección proyectos a "projects" y los de la sección contacto a "contact".

// src/components/About.js

import React from "react";

export default function About() {
  return (
    <section id="about">
      <div className="container mx-auto flex px-10 py-20 md:flex-row flex-col items-center">
        <div className="lg:flex-grow md:w-1/2 lg:pr-24 md:pr-16 flex flex-col md:items-start md:text-left mb-16 md:mb-0 items-center text-center">
          <h1 className="title-font sm:text-4xl text-3xl mb-4 font-medium text-white">
            Hola, soy Reed
            <br className="hidden lg:inline-block" />Me encanta construir aplicaciones web.
          </h1>
          <p className="mb-8 leading-relaxed">
            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Qui
            laborum quasi, incidunt dolore iste nostrum cupiditate voluptas?
            Laborum, voluptas natus?
          </p>
          <div className="flex justify-center">
            <a
              href="#contact"
              className="inline-flex text-white bg-green-500 border-0 py-2 px-6 focus:outline-none hover:bg-green-600 rounded text-lg">
              Trabaja conmigo
            </a>
            <a
              href="#projects"
              className="ml-4 inline-flex text-gray-400 bg-gray-800 border-0 py-2 px-6 focus:outline-none hover:bg-gray-700 hover:text-white rounded text-lg">
              Mira mis trabajos anteriores
            </a>
          </div>
        </div>
        <div className="lg:max-w-lg lg:w-full md:w-1/2 w-5/6">
          <img
            className="object-cover object-center rounded"
            alt="hero"
            src="./coding.svg"
          />
        </div>
      </div>
    </section>
  );
}

Para la imagen en el lado derecho de la sección, utilizaremos un archivo svg tomado de la carpeta public: coding.svg.

Esta imagen funciona simplemente como un placeholder (marcador de posición) temporal. Recomiendo enfáticamente utilizar una imagen real de ti mismo.

Cómo construir el componente Projects

Nuestra sección de proyectos consistirá de un elemento section con un id of "projects". La sección mostrará una galería de todos los proyectos que hemos desarrollado, con imágenes incluidas.

Tendrá el título del proyecto, las tecnologías con la que lo construimos, y un enlace (si está desplegado).

// src/components/Projects.js

import { CodeIcon } from "@heroicons/react/solid";
import React from "react";
import { projects } from "../data";

export default function Projects() {
  return (
    <section id="projects" className="text-gray-400 bg-gray-900 body-font">
      <div className="container px-5 py-10 mx-auto text-center lg:px-40">
        <div className="flex flex-col w-full mb-20">
          <CodeIcon className="mx-auto inline-block w-10 mb-4" />
          <h1 className="sm:text-4xl text-3xl font-medium title-font mb-4 text-white">
            Aplicaciones que he construido
          </h1>
          <p className="lg:w-2/3 mx-auto leading-relaxed text-base">
            Lorem ipsum, dolor sit amet consectetur adipisicing elit. Explicabo
            facilis repellat ab cupiditate alias vero aliquid obcaecati quisquam
            fuga dolore.
          </p>
        </div>
        <div className="flex flex-wrap -m-4">
          {projects.map((project) => (
            <a
              href={project.link}
              key={project.image}
              className="sm:w-1/2 w-100 p-4">
              <div className="flex relative">
                <img
                  alt="gallery"
                  className="absolute inset-0 w-full h-full object-cover object-center"
                  src={project.image}
                />
                <div className="px-8 py-10 relative z-10 w-full border-4 border-gray-800 bg-gray-900 opacity-0 hover:opacity-100">
                  <h2 className="tracking-widest text-sm title-font font-medium text-green-400 mb-1">
                    {project.subtitle}
                  </h2>
                  <h1 className="title-font text-lg font-medium text-white mb-3">
                    {project.title}
                  </h1>
                  <p className="leading-relaxed">{project.description}</p>
                </div>
              </div>
            </a>
          ))}
        </div>
      </div>
    </section>
  );
}

También utilizaremos la librería @heroicons/react para poder acceder a iconos SVG como componentes de React.

Estaremos importando un arreglo (array) de proyectos del archivo data.js en la misma carpeta. Allí estamos exportando un arreglo de objetos que incluirá cada uno la data individual de cada proyecto:

// src/data.js

export const projects = [
  {
    title: "React Reserve",
    subtitle: "MERN Stack",
    description:
      "Lorem ipsum dolor sit amet consectetur adipisicing elit. Praesentium dolore rerum laborum iure enim sint nemo omnis voluptate exercitationem eius?",
    image: "./project-1.gif",
    link: "https://reactbootcamp.com",
  },
  {
    title: "React Tracks",
    subtitle: "React and Python",
    description:
      "Lorem ipsum dolor sit amet consectetur adipisicing elit. Praesentium dolore rerum laborum iure enim sint nemo omnis voluptate exercitationem eius?",
    image: "./project-2.gif",
    link: "https://reedbarger.com",
  },
  {
    title: "DevChat",
    subtitle: "React and Firebase",
    description:
      "Lorem ipsum dolor sit amet consectetur adipisicing elit. Praesentium dolore rerum laborum iure enim sint nemo omnis voluptate exercitationem eius?",
    image: "./project-3.gif",
    link: "https://jsbootcamp.com",
  },
  {
    title: "Epic Todo App",
    subtitle: "React Hooks",
    description:
      "Lorem ipsum dolor sit amet consectetur adipisicing elit. Praesentium dolore rerum laborum iure enim sint nemo omnis voluptate exercitationem eius?",
    image: "./project-4.gif",
    link: "https://pythonbootcamp.com",
  },
];

Cómo construir el componente Skills

Completemos la sección de todas nuestras habilidades y las tecnologías que manejamos.

Esta sección consistirá de una simple lista de todas las herramientas con las que estamos familiarizados y seremos capaces de utilizar en los proyectos de nuestros empleadores o clientes.

Una vez más, importaremos un arreglo desde nuestra carpeta de data. Pero este arreglo consiste de un número de cadenas (strings) que representan cada una de las habilidades como JavaScript, React y Node:

// src/components/Skills.js

import { BadgeCheckIcon, ChipIcon } from "@heroicons/react/solid";
import React from "react";
import { skills } from "../data";

export default function Skills() {
  return (
    <section id="skills">
      <div className="container px-5 py-10 mx-auto">
        <div className="text-center mb-20">
          <ChipIcon className="w-10 inline-block mb-4" />
          <h1 className="sm:text-4xl text-3xl font-medium title-font text-white mb-4">
            Habilidades y Tecnologías
          </h1>
          <p className="text-base leading-relaxed xl:w-2/4 lg:w-3/4 mx-auto">
            Lorem ipsum dolor sit amet consectetur, adipisicing elit. Nisi sit
            ipsa delectus eum quo voluptas aspernatur accusantium distinctio
            possimus est.
          </p>
        </div>
        <div className="flex flex-wrap lg:w-4/5 sm:mx-auto sm:mb-2 -mx-2">
          {skills.map((skill) => (
            <div key={skill} className="p-2 sm:w-1/2 w-full">
              <div className="bg-gray-800 rounded flex p-4 h-full items-center">
                <BadgeCheckIcon className="text-green-400 w-6 h-6 flex-shrink-0 mr-4" />
                <span className="title-font font-medium text-white">
                  {skill}
                </span>
              </div>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
}

Cómo construir el componente Testimonials

En el componente de testimonios, crearemos una lista con testimonios de antiguos clientes u otras personas que estén familiarizadas con nuestro trabajo.

Estos consistirán de tarjetas que incluirán el testimonio, así como el autor y la compañía para la que esta persona trabaja.

También importaremos un arreglo de testimonios con un número de objetos que incluirán la cita, imagen, nombre y compañía.

// src/components/Testimonials

import React from "react";
import { TerminalIcon, UsersIcon } from "@heroicons/react/solid";
import { testimonials } from "../data";

export default function Testimonials() {
  return (
    <section id="testimonials">
      <div className="container px-5 py-10 mx-auto text-center">
        <UsersIcon className="w-10 inline-block mb-4" />
        <h1 className="sm:text-4xl text-3xl font-medium title-font text-white mb-12">
          Testimonios de clientes
        </h1>
        <div className="flex flex-wrap m-4">
          {testimonials.map((testimonial) => (
            <div className="p-4 md:w-1/2 w-full">
              <div className="h-full bg-gray-800 bg-opacity-40 p-8 rounded">
                <TerminalIcon className="block w-8 text-gray-500 mb-4" />
                <p className="leading-relaxed mb-6">{testimonial.quote}</p>
                <div className="inline-flex items-center">
                  <img
                    alt="testimonial"
                    src={testimonial.image}
                    className="w-12 rounded-full flex-shrink-0 object-cover object-center"
                  />
                  <span className="flex-grow flex flex-col pl-4">
                    <span className="title-font font-medium text-white">
                      {testimonial.name}
                    </span>
                    <span className="text-gray-500 text-sm uppercase">
                      {testimonial.company}
                    </span>
                  </span>
                </div>
              </div>
            </div>
          ))}
        </div>
      </div>
    </section>
  );
}

Cómo construir el componente contact

Al final de nuestra página, incluiremos un formulario de contacto para así permitir e los potenciales empleadores comunicarse con nosotros.

Este formulario tendrá tres campos: nombre, correo electrónico y mensaje.

Para recibir esta información, utilizaremos la herramienta Netlify Forms, que se encargará fácilmente de guardar estos mensajes.

// src/components/Contact.js

import React from "react";

export default function Contact() {
  return (
    <section id="contact" className="relative">
      <div className="container px-5 py-10 mx-auto flex sm:flex-nowrap flex-wrap">
        <div className="lg:w-2/3 md:w-1/2 bg-gray-900 rounded-lg overflow-hidden sm:mr-10 p-10 flex items-end justify-start relative">
          <iframe
            width="100%"
            height="100%"
            title="map"
            className="absolute inset-0"
            frameBorder={0}
            marginHeight={0}
            marginWidth={0}
            style={{ filter: "opacity(0.7)" }}
            src="https://www.google.com/maps/embed/v1/place?q=97+warren+st+new+york+city&key=AIzaSyBFw0Qbyq9zTFTd-tUY6dZWTgaQzuU17R8"
          />
          <div className="bg-gray-900 relative flex flex-wrap py-6 rounded shadow-md">
            <div className="lg:w-1/2 px-6">
              <h2 className="title-font font-semibold text-white tracking-widest text-xs">
                DIRECCIÓN
              </h2>
              <p className="mt-1">
                97 Warren St. <br />
                New York, NY 10007
              </p>
            </div>
            <div className="lg:w-1/2 px-6 mt-4 lg:mt-0">
              <h2 className="title-font font-semibold text-white tracking-widest text-xs">
                CORREO ELECTRÓNICO
              </h2>
              <a className="text-indigo-400 leading-relaxed">
                reedbarger@email.com
              </a>
              <h2 className="title-font font-semibold text-white tracking-widest text-xs mt-4">
                NÚMERO DE TELÉFONO
              </h2>
              <p className="leading-relaxed">123-456-7890</p>
            </div>
          </div>
        </div>
        <form
          netlify
          name="contact"
          className="lg:w-1/3 md:w-1/2 flex flex-col md:ml-auto w-full md:py-8 mt-8 md:mt-0">
          <h2 className="text-white sm:text-4xl text-3xl mb-1 font-medium title-font">
            ¡Contrátame!
          </h2>
          <p className="leading-relaxed mb-5">
            Lorem ipsum dolor sit amet consectetur, adipisicing elit. Illum
            suscipit officia aspernatur veritatis. Asperiores, aliquid?
          </p>
          <div className="relative mb-4">
            <label htmlFor="name" className="leading-7 text-sm text-gray-400">
              Nombre
            </label>
            <input
              type="text"
              id="name"
              name="name"
              className="w-full bg-gray-800 rounded border border-gray-700 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-900 text-base outline-none text-gray-100 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out"
            />
          </div>
          <div className="relative mb-4">
            <label htmlFor="email" className="leading-7 text-sm text-gray-400">
              Correo electrónico
            </label>
            <input
              type="email"
              id="email"
              name="email"
              className="w-full bg-gray-800 rounded border border-gray-700 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-900 text-base outline-none text-gray-100 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out"
            />
          </div>
          <div className="relative mb-4">
            <label
              htmlFor="message"
              className="leading-7 text-sm text-gray-400">
              Mensaje
            </label>
            <textarea
              id="message"
              name="message"
              className="w-full bg-gray-800 rounded border border-gray-700 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-900 h-32 text-base outline-none text-gray-100 py-1 px-3 resize-none leading-6 transition-colors duration-200 ease-in-out"
            />
          </div>
          <button
            type="submit"
            className="text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-none hover:bg-indigo-600 rounded text-lg">
            Enviar
          </button>
        </form>
      </div>
    </section>
  );
}

Cómo insertar una ubicación de Google Maps

A la izquierda del formulario incluiremos un mapa de Google Maps con nuestra ubicación.

Lo haremos con la ayuda de una herramienta online: embed-map.com. Todo lo que hay que hacer es ingresar la ubicación y presionar "Generate HTML code".

Del código proporcionado por la aplicación, no debemos copiar la totalidad, sólo el atributo src del elemento iframe. Reemplazaremos dicho valor con el valor por defecto src de nuestro iframe.

portfolio-2

Para enviar cualquier data del formulario a Netlify, Netlify Forms necesita reconocer un formulario como HTML estático. Dado que nuestra aplicación de React es controlada por JavaScript y no es únicamente HTML puro, necesitamos agregar un formulario escondido a nuestro archivo index.html en la carpeta public.

<!-- public/index.html -->

<!DOCTYPE html>
<html lang="en">
  <head>
    <!-- head content skipped -->
  </head>
  <body>

  <form name="contact" netlify netlify-honeypot="bot-field" hidden>
    <input type="text" name="name" />
    <input type="email" name="email" />
    <textarea name="message"></textarea>
  </form>
  
    <noscript>Necesitas activar JavaScript para correr esta aplicación.</noscript>
    <div id="root"></div>
  </body>
</html>

Tendremos que esconder este formulario, porque no debe ser visto por el usuario, solo por Netlify.

Le daremos el atributo, hidden así como el atributo name que concuerda con el formulario JSX en Contact.js. También debemos darle el atributo netlify para que así Netlify Forms lo reconozca. Finalmente, debemos incluir todos los campos que nuestro formulario JSX: nombre, correo electrónico y mensaje.

Cómo enviar el formulario de contacto

Una vez terminado lo anterior, regresaremos a Contact.js. Utilizaremos JavaScript para poder enviar este formulario.

Primero que nada, crearemos un estado dedicado para cada uno de los valores que se encuentran en el formulario para nombre (name), correo electrónico (email) y mensaje (message):

const [name, setName] = React.useState("");
const [email, setEmail] = React.useState("");
const [message, setMessage] = React.useState("");

Almacenaremos lo que el usuario escriba en cada uno de los campos en el estado con la ayuda del manipulador (handler) onChange.

Para manipular el envío del formulario, le agregaremos el prop onSubmit. La función que se llamará, handleSubmit, hará una solicitud post al endpoint "/" con toda la data de nuestro formulario.

Estableceremos los encabezados de la petición para indicar que nos encontramos enviando la data del formulario. Para el cuerpo de la petición, incluiremos el nombre del formulario así como toda la data del formulario incluida en las variables de estado name, email, y message.

// src/components/Contact.js

import React from "react";

export default function Contact() {
  const [name, setName] = React.useState("");
  const [email, setEmail] = React.useState("");
  const [message, setMessage] = React.useState("");

  function encode(data) {
    return Object.keys(data)
      .map(
        (key) => encodeURIComponent(key) + "=" + encodeURIComponent(data[key])
      )
      .join("&");
  }

  function handleSubmit(e) {
    e.preventDefault();
    fetch("/", {
      method: "POST",
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
      body: encode({ "form-name": "contact", name, email, message }),
    })
      .then(() => alert("Message sent!"))
      .catch((error) => alert(error));
  }

  return (
    <section id="contact" className="relative">
      <div className="container px-5 py-10 mx-auto flex sm:flex-nowrap flex-wrap">
        <div className="lg:w-2/3 md:w-1/2 bg-gray-900 rounded-lg overflow-hidden sm:mr-10 p-10 flex items-end justify-start relative">
          <iframe
            width="100%"
            height="100%"
            title="map"
            className="absolute inset-0"
            frameBorder={0}
            marginHeight={0}
            marginWidth={0}
            style={{ filter: "opacity(0.7)" }}
            src="https://www.google.com/maps/embed/v1/place?q=97+warren+st+new+york+city&key=AIzaSyBFw0Qbyq9zTFTd-tUY6dZWTgaQzuU17R8"
          />
          <div className="bg-gray-900 relative flex flex-wrap py-6 rounded shadow-md">
            <div className="lg:w-1/2 px-6">
              <h2 className="title-font font-semibold text-white tracking-widest text-xs">
                DIRECCIÓN
              </h2>
              <p className="mt-1">
                97 Warren St. <br />
                New York, NY 10007
              </p>
            </div>
            <div className="lg:w-1/2 px-6 mt-4 lg:mt-0">
              <h2 className="title-font font-semibold text-white tracking-widest text-xs">
                CORREO ELECTRÓNICO
              </h2>
              <a className="text-indigo-400 leading-relaxed">
                reedbarger@email.com
              </a>
              <h2 className="title-font font-semibold text-white tracking-widest text-xs mt-4">
                NÚMERO DE TELÉFONO
              </h2>
              <p className="leading-relaxed">123-456-7890</p>
            </div>
          </div>
        </div>
        <form
          netlify
          name="contact"
          onSubmit={handleSubmit}
          className="lg:w-1/3 md:w-1/2 flex flex-col md:ml-auto w-full md:py-8 mt-8 md:mt-0">
          <h2 className="text-white sm:text-4xl text-3xl mb-1 font-medium title-font">
            ¡Contrátame!
          </h2>
          <p className="leading-relaxed mb-5">
            Lorem ipsum dolor sit amet consectetur, adipisicing elit. Illum
            suscipit officia aspernatur veritatis. Asperiores, aliquid?
          </p>
          <div className="relative mb-4">
            <label htmlFor="name" className="leading-7 text-sm text-gray-400">
              Nombre
            </label>
            <input
              type="text"
              id="name"
              name="name"
              className="w-full bg-gray-800 rounded border border-gray-700 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-900 text-base outline-none text-gray-100 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out"
              onChange={(e) => setName(e.target.value)}
            />
          </div>
          <div className="relative mb-4">
            <label htmlFor="email" className="leading-7 text-sm text-gray-400">
              Correo electrónico
            </label>
            <input
              type="email"
              id="email"
              name="email"
              className="w-full bg-gray-800 rounded border border-gray-700 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-900 text-base outline-none text-gray-100 py-1 px-3 leading-8 transition-colors duration-200 ease-in-out"
              onChange={(e) => setEmail(e.target.value)}
            />
          </div>
          <div className="relative mb-4">
            <label
              htmlFor="message"
              className="leading-7 text-sm text-gray-400">
              Mensaje
            </label>
            <textarea
              id="message"
              name="message"
              className="w-full bg-gray-800 rounded border border-gray-700 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-900 h-32 text-base outline-none text-gray-100 py-1 px-3 resize-none leading-6 transition-colors duration-200 ease-in-out"
              onChange={(e) => setMessage(e.target.value)}
            />
          </div>
          <button
            type="submit"
            className="text-white bg-indigo-500 border-0 py-2 px-6 focus:outline-none hover:bg-indigo-600 rounded text-lg">
            Enviar
          </button>
        </form>
      </div>
    </section>
  );
}

Como puedes observar arriba, estamos codificando la data del formulario con una función especial encode que puedes ver aquí.

Si el mensaje es enviado correctamente, mostraremos una alerta que diga "Mensaje enviado". En caso de que haya un error, alertaremos al usuario del mismo.

Cómo construir el componente Navbar

El último paso es construir el componente de la Barra de Navegación.

Queremos que la barra de navegación se mantenga en la parte superior de la pantalla en dispositivos grandes, pero no en dispositivos móviles.

Adicionalmente, querremos incluir enlaces a cada una de las secciones relevantes: proyectos, habilidades, testimonios y formulario de contacto:

// src/components/Navbar.js

import { ArrowRightIcon } from "@heroicons/react/solid";
import React from "react";

export default function Navbar() {
  return (
    <header className="bg-gray-800 md:sticky top-0 z-10">
      <div className="container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center">
        <a className="title-font font-medium text-white mb-4 md:mb-0">
          <a href="#about" className="ml-3 text-xl">
            Reed Barger
          </a>
        </a>
        <nav className="md:mr-auto md:ml-4 md:py-1 md:pl-4 md:border-l md:border-gray-700	flex flex-wrap items-center text-base justify-center">
          <a href="#projects" className="mr-5 hover:text-white">
            Trabajos anteriores
          </a>
          <a href="#skills" className="mr-5 hover:text-white">
            Habilidades
          </a>
          <a href="#testimonials" className="mr-5 hover:text-white">
            Testimonios
          </a>
        </nav>
        <a
          href="#contact"
          className="inline-flex items-center bg-gray-800 border-0 py-1 px-3 focus:outline-none hover:bg-gray-700 rounded text-base mt-4 md:mt-0">
          ¡Contrátame!
          <ArrowRightIcon className="w-4 h-4 ml-1" />
        </a>
      </div>
    </header>
  );
}

¿Cómo haremos para mantener la barra de navegación en la parte superior de la pantalla en dispositivos grandes? Con la ayuda de la clase md:sticky en nuestro elemento header.

Esta clase significa que se aplicará la regla de estilo position: sticky; a partir del punto de corte mediano (768px).

Cómo desplegar tu Portafolio

Ahora, para desplegar el portafolio en la web, necesitaremos cargar (push) nuestra aplicación a GitHub.

Si no estás familiarizado con Git y GitHub, deberías tomarte unos momentos para aprender a cargar tu código en GitHub por primera vez. Es una habilidad esencial que todo desarrollador web debe tener.

Una vez estés familiarizado con este proceso, podemos crear un nuevo repositorio de GitHub. Luego, correremos los comandos git add ., git commit -m "Despliegue", crear nuestro git remoto, y git push -u origin master.

Una vez nuestro proyecto esté en GitHub, podremos ir a Netlify y seleccionar la opción "Choose Site from Git" (Escoger sitio desde Git). Allí seleccionaremos GitHub para nuestros despliegues continuados, y seleccionaremos el repositorio de GitHub al que acabamos de cargar nuestro código.

portfolio-3-min

Luego de esto, ¡nuestro proyecto será desplegado automáticamente a la web!

Próximos pasos

¡Felicidades! Ya tienes tu propio portafolio personal desplegado en la web, mostrando todos tus proyectos y habilidades a potenciales empleadores.

El próximo paso será configurar un dominio personalizado, preferiblemente con tu nombre (ej. reedbarger.com). Dado que Netlify incluye un DNS (Sistema de Nombres de Dominios, por sus siglas en inglés), esto podrás hacerlo fácilmente con sus herramientas.

Piensa en quizás agregar un blog a tu aplicación de React para así presumir aún más de tus conocimientos como desarrollador ante potenciales empleadores.

Haz que tu portafolio personal sea una expresión de ti mismo y lo que te apasiona como desarrollador, y serás exitoso.