Сегодня вы собираетесь создать одно из самых важных приложений, которое вы можете создать для себя: свое портфолио разработчика.

Каждый разработчик React или веб-разработчик в целом должен иметь возможность продемонстрировать, что они могут сделать, любому потенциальному клиенту или работодателю.

Это именно то, что мы собираемся создать прямо сейчас, с помощью ряда стандартных отраслевых инструментов, включая React, Tailwind CSS и Netlify.

Давайте начнем!

Как это будет выглядеть?

Это последняя версия портфолио, которое вы будете строить.

В нем будет представлена ​​информация о нас самих, о том, какие проекты мы реализовали, какие навыки мы использовали при выполнении этих проектов, а также контактная форма, чтобы клиенты или работодатели могли связаться с нами.

Какие инструменты мы будем использовать?

  • Для создания пользовательского интерфейса приложения мы будем использовать React. Это позволит нам составлять каждую часть нашей целевой страницы с помощью компонентов многократного использования и расширять наше приложение, если мы хотим добавить дополнительные функции, такие как блог.
  • Чтобы стилизовать наше приложение, мы будем использовать Tailwind CSS. Чтобы придать нашему приложению профессиональный вид, Tailwind позволит нам легко применять несколько стилей, комбинируя имена классов в наших элементах React.
  • Для публикации нашего приложения в сети мы воспользуемся бесплатным сервисом Netlify. Он будет обслуживать наш проект в персональном домене для пользователей очень быстро с помощью CDN (сети доставки контента).

С чего начать

Вы можете скачать стартовые файлы для нашего проекта здесь.

Когда вы возьмете код, все, что вам нужно будет сделать, это перетащить свою (разархивированную) папку проекта в редактор кода и выполнить команду:

npm install

И тебе хорошо!

Какие инструменты мне нужны?

Чтобы пройти весь процесс создания нашего приложения от начала до развертывания, вам потребуется следующее:

  1. Node.js установлен на вашем компьютере. Вы можете скачать его на nodejs.org.
  2. Git установлен на вашем компьютере. Вы можете скачать его на git-scm.com.
  3. Я бы порекомендовал вам использовать VS Code в качестве редактора кода. Вы можете скачать его на code.visualstudio.com.
  4. Бесплатная учетная запись Netlify на netlify.com.
  5. Бесплатная учетная запись Github на github.com.

Построение структуры портфеля

Преимущество использования React заключается в том, что мы можем очень просто расширить наше приложение до любого количества страниц и добавить много дополнительного контента.

Однако, поскольку мы работаем только с одной страницей, мы можем в рамках нашего компонента приложения очень быстро определить различные компоненты, которые нам нужны. Наверху у нас будет панель навигации со всеми ссылками для перехода к различным разделам нашего портфолио.

После этого мы добавим раздел «О нас», раздел для наших проектов, отзывов и, наконец, нашу контактную форму.

Такое быстрое планирование позволяет нам понять, какие компоненты должны быть названы и в каком порядке. Мы можем продолжить и все это в наш файл App.js (в src):

// src/App.js

import React from "react";

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

Создание наших компонентов

Теперь, когда у нас есть все перечисленные компоненты, нам нужно продолжить и создать их.

В нашей исходной папке (src) мы собираемся создать папку под названием components со всеми необходимыми файлами:

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

Затем мы создадим базовую структуру каждого компонента React и экспортируем ее из этого файла с помощью export default:

// src/components/About.js

export default function About() {}

// repeat the same basic structure for all 6 components

И, наконец, не забудьте импортировать его обратно в 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>
  );
}

Обратите внимание, что всего должно быть шесть компонентов.

Введение в Tailwind CSS

Как только это будет сделано, мы можем начать работу с Tailwind CSS, чтобы придать нашему приложению базовый вид.

Преимущество использования Tailwind CSS заключается в том, что нам не нужно вручную писать какие-либо стили в таблице стилей CSS. Все, что нам нужно сделать, это объединить несколько классов, чтобы создать желаемый внешний вид.

Например, если дать нашему портфолио темный фон с серым текстом, примененным ко всем нашим дочерним компонентам, вы можете добавить следующие классы к нашему элементу 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>
  );
}

О Компоненте

Давайте начнем с нашего первого раздела - раздела «О нас». Это будет состоять из базового представления о себе и о том, на каких навыках мы специализируемся.

Он также будет включать некоторые ссылки на контактную форму, а также на наши прошлые проекты. Поскольку эти ссылки будут вести к разным частям одной и той же страницы, мы можем использовать хеши: «/ # projects» и «/ # contact».

Чтобы эти ссылки работали и можно было переходить к каждому разделу, мы установим для атрибута id раздела проектов значение «проекты», а для раздела контактов - значение «контакт».

// 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">
            Hi, I'm Reed.
            <br className="hidden lg:inline-block" />I love to build amazing
            apps.
          </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">
              Work With Me
            </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">
              See My Past Work
            </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>
  );
}

Для изображения в правой части раздела я использую файл svg из папки public, coding.svg.

Это изображение служит лишь временным заполнителем; Я настоятельно рекомендую использовать собственное изображение.

Компонент "Проекты"

Наш раздел проектов будет состоять из section элемента с id "проектами". В нем будет представлена ​​галерея всех созданных нами проектов, в которую будут входить изображения.

Название проекта, а также технологии, которые мы используем для его создания, и ссылка на него (если он развернут).

// 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">
            Apps I've Built
          </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>
  );
}

Обратите внимание, что мы также собираемся использовать библиотеку @heroicons/react, чтобы иметь возможность писать некоторые значки SVG в качестве компонентов React.

Мы импортируем массив проектов из файла data.js в той же папке. Там мы экспортируем массив объектов, каждый из которых включает данные отдельного проекта:

// 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",
  },
];

Компонент навыков

Давайте заполним этот раздел всеми навыками и технологиями, которые нам известны.

Он будет состоять из простого списка всех основных инструментов, с которыми мы знакомы и которые можем использовать в проектах наших работодателей или клиентов.

Еще раз, мы собираемся импортировать массив из нашей папки данных, однако этот массив состоит из ряда строк, которые представляют каждый из известных нам навыков, таких как JavaScript, React и 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">
            Skills &amp; Technologies
          </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>
  );
}

Компонент "Отзывы"

В компоненте «Отзывы» мы собираемся перечислить несколько отзывов, возможно, от прошлых клиентов или людей, знакомых с нашей работой.

Они будут состоять из пары карточек, на которых будет изображен сам отзыв, а также его имя и компания, из которой принадлежит этот человек.

Мы также импортируем массив отзывов с рядом объектов, содержащих цитату, изображение, имя и компанию.

// 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">
          Client Testimonials
        </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>
  );
}

Контактный компонент

В конце целевой страницы мы разместим нашу контактную форму, чтобы потенциальные работодатели могли связаться с нами.

Эта форма будет иметь 3 поля ввода: имя, адрес электронной почты и сообщение.

Чтобы получать эти отправленные формы, мы будем использовать инструмент Netlify Forms, который очень легко позаботится о сохранении этих сообщений.

// 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">
                ADDRESS
              </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">
                EMAIL
              </h2>
              <a className="text-indigo-400 leading-relaxed">
                [email protected]
              </a>
              <h2 className="title-font font-semibold text-white tracking-widest text-xs mt-4">
                PHONE
              </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">
            Hire Me
          </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">
              Name
            </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">
              Email
            </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">
              Message
            </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">
            Submit
          </button>
        </form>
      </div>
    </section>
  );
}

Вставить карту Google

Слева от формы мы включим встроенную карту Google Maps того места, где мы находимся.

Мы можем сделать это с помощью онлайн-инструмента: embed-map.com. Все, что вам нужно сделать, это просто ввести свое местоположение и нажать «Создать HTML-код».

В данном коде копируйте не весь код, а только атрибут src из элемента iframe. Мы заменим это значение значением по умолчанию src, используемым для нашего iframe.

Чтобы отправить любые отправленные данные формы в Netlify, Netlify Forms должен распознать форму как статический HTML. Поскольку наше приложение React управляется JavaScript, состоит и не состоит из простого HTML, нам нужно добавить скрытую форму в наш файл index.html в общей папке.

<!-- 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>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>

Нам нужно скрыть эту форму, потому что она не должна быть видна пользователю, только Netlify.

Мы дадим ему атрибут hidden, а также атрибут name, который соответствует форме JSX в Contact.js. Нам также нужно присвоить ему атрибут netlify, чтобы он распознавался Netlify Forms. Наконец, нам нужно включить все те же входные данные, что и в нашей JSX-форме: для имени, электронной почты, сообщения.

Отправка нашей контактной формы

Как только это будет сделано, мы вернемся к Contact.js. Мы собираемся использовать JavaScript для отправки этой формы.

Прежде всего, мы собираемся создать отдельное состояние для каждого из значений, которые вводятся в форме для имени, электронной почты и сообщения:

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

Мы будем сохранять то, что пользователь вводит для каждого из входов в состоянии, с помощью обработчика onChange.

Чтобы обработать отправку формы, мы добавим к ней опору onSubmit. Функция, которая будет вызываться, handleSubmit, отправит почтовый запрос к конечной точке '/' со всеми данными нашей формы.

Мы установим заголовки запроса, чтобы указать, что мы отправляем данные формы, а для тела запроса мы включим имя формы, а также все данные формы из переменных состояния name, email и 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">
                ADDRESS
              </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">
                EMAIL
              </h2>
              <a className="text-indigo-400 leading-relaxed">
                [email protected]
              </a>
              <h2 className="title-font font-semibold text-white tracking-widest text-xs mt-4">
                PHONE
              </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">
            Hire Me
          </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">
              Name
            </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">
              Email
            </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">
              Message
            </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">
            Submit
          </button>
        </form>
      </div>
    </section>
  );
}

Как вы можете видеть выше, мы кодируем данные формы с помощью специальной encode функции, которую вы видите здесь.

Если сообщение отправлено правильно, мы отобразим предупреждение с надписью
«Сообщение отправлено». В противном случае, если есть ошибка, мы будем предупреждать пользователя об этой ошибке.

Компонент панели навигации

Последний шаг - создать наш компонент Navbar.

Мы хотим, чтобы эта панель навигации находилась в верхней части нашего приложения на больших устройствах и не закреплялась на мобильных устройствах.

Кроме того, мы хотим включить ссылки на каждый из наших соответствующих разделов с отзывами о наших навыках проекта и нашу контактную форму:

// 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">
            Past Work
          </a>
          <a href="#skills" className="mr-5 hover:text-white">
            Skills
          </a>
          <a href="#testimonials" className="mr-5 hover:text-white">
            Testimonials
          </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">
          Hire Me
          <ArrowRightIcon className="w-4 h-4 ml-1" />
        </a>
      </div>
    </header>
  );
}

Как это закрепляется в верхней части страницы на большом устройстве? С помощью класса md:sticky в нашем элементе header.

Этот класс означает, что к нему будет применяться правило стиля position: sticky;, начиная с точки останова среднего размера (768 пикселей).

Разверните свое портфолио

Теперь, чтобы воплотить наше портфолио в жизнь, нам нужно отправить наше приложение на GitHub.

Если вы не знакомы с Git и GitHub, мне бы потребовалось немного времени, чтобы узнать, как впервые разместить ваш код в учетной записи GitHub. Это важный навык, который должен знать любой разработчик.

Когда вы ознакомитесь с этим процессом, мы сможем сначала создать новый репозиторий Github. После этого мы запустим git add ., git commit -m "Deploy", создадим наш git remote и git push -u origin master.

Как только наш проект будет размещен на GitHub, мы можем перейти на Netlify и выбрать опцию «Выбрать сайт из Git». Затем мы выберем GitHub для непрерывного развертывания, выберем репозиторий GitHub, в который мы только что поместили наш код.

После этого наш проект будет автоматически развернут в сети!

Что дальше

Поздравляю! Теперь у вас есть приложение-портфолио в Интернете, которое демонстрирует все ваши проекты и навыки потенциальным работодателям.

Следующим шагом будет создание личного домена, желательно с вашим именем (например, reedbarger.com). Поскольку Netlify включает в себя DNS, вы можете легко настроить с ними собственный домен.

Возможно, стоит подумать о добавлении блога в приложение React, чтобы продемонстрировать потенциальным работодателям еще больше своих знаний разработчика.

Сделайте свое личное портфолио выражением себя и того, чем вы увлечены как разработчик, и у вас все получится!

Проверенный способ научиться реагировать в рекордно короткие сроки

React Bootcamp объединяет все, что вы должны знать об изучении React, в один комплексный пакет, включая видео, шпаргалки и специальные бонусы.

Получите инсайдерскую информацию, которую сотни разработчиков уже использовали, чтобы освоить React, найти работу своей мечты и взять под контроль свое будущее:

Нажмите здесь, чтобы получить уведомление, когда оно откроется