Сброс пароля с помощью Axios, Next.js и useRouter Hook

Узнайте, как реализовать функцию сброса пароля с помощью Axios, useRouter Hook и динамических маршрутов Next.js, отдавая приоритет UX.

Большинство пользователей, как правило, забывают свои пароли, поэтому создавайте возможности, которые всегда будут ценны для людей, использующих ваши продукты. В этой статье мы обсудим, как реализовать функцию сброса пароля с динамическими маршрутами Next.js, отдавая приоритет UX.

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

Хотя существует множество ресурсов, посвященных созданию аутентифицированных приложений с помощью Next.js. Ну, есть даже проект с открытым исходным кодом , который буквально обрабатывает аутентификацию с нуля.

Но рамки этой статьи не охватывают всю концепцию аутентификации. Мы выбираем только конкретный шаблон процесса аутентификации: поток «сброса пароля» и то, как его можно реализовать на стороне клиента — интерфейсе — веб-приложения.

В этой статье вы увидите, как эту функцию можно реализовать с помощью распространенного инструмента выборки данных Axios — встроенной функции динамических маршрутов Next.js и useRouterхука.

Обзор процедуры сброса пароля 

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

Есть такая популярная поговорка: «Пользователи всегда будут забывать свои пароли», и это абсолютная правда. Многие люди даже боятся страницы «сброс пароля», потому что, если подумать, после того, как они потратили много времени, пытаясь угадать свои пароли — все безрезультатно — они либо расстроены, либо злы, когда попадают на эту страницу. конкретная страница.

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

Общий поток процесса сброса пароля можно увидеть ниже.

  • Пользователь расстроен после безуспешной попытки подписать. Они нажимают на ссылку «сброс пароля» и перенаправляются на соответствующую страницу. Пользовательский интерфейс, который они видят, представляет собой типичную веб-форму, которая принимает либо их адрес электронной почты, либо имя пользователя.
  • Когда они вводят свой адрес электронной почты или имя пользователя в поле ввода, они нажимают кнопку с обычным текстом «отправьте мне ссылку для восстановления по электронной почте».
  • Они получают подтверждение того, что безопасная ссылка была отправлена ​​на их электронную почту. Иногда этот текст подтверждения может отображаться в карточном компоненте или в модальном окне, которое со временем исчезает.
     

Примечание . В целях безопасности и хорошего UX хорошо использовать текст, очень похожий на этот: «На ваш почтовый ящик отправлено электронное письмо. Пожалуйста, нажмите на ссылку, когда получите ее». Вы можете построить это предложение так, как считаете нужным, если оно не показывает, что введенный им адрес электронной почты или имя пользователя существует в базе данных. Такой подход не позволяет злоумышленникам узнать, существует ли вообще это электронное письмо, тем самым саботируя любые попытки фишинга, которые они могут предпринять с указанным адресом электронной почты. Что касается UX, текст не гарантирует пользователю, что введенные им учетные данные являются правильными. Это, в свою очередь, позволяет им дважды проверять любые учетные данные, которые они отправляют.

  • Ссылка, отправленная на их адрес электронной почты, содержит JWT и их user_idили, в данном случае, их адрес электронной почты.
  • При нажатии на эту ссылку они перенаправляются на маршрут/страницу, где они могут ввести свой новый пароль. Маршрут, по которому будет идти пользователь, может иметь форму ниже
https://localhost:3000/reset-password/user-email/JWToken
  • Последняя часть процесса — проверка того, связан ли созданный JWT с учетной записью пользователя. Если нет, мы выдаем ошибку, отображая сообщение об ошибке, полученное от бэкэнда.

Теперь, когда вы увидели, как устроен процесс «сброса пароля», давайте посмотрим, как его можно реализовать с помощью Next.js.

Понимание динамических маршрутов 

В этом разделе мы рассмотрим концепцию динамических маршрутов, проиллюстрировав ее структурой папок проекта Next.js, и посмотрим, как мы интегрируем ее в функцию «сброса пароля». Но сначала давайте настроим приложение Next.js.

npx create-next-app app-name

Приведенная выше команда делает это за нас. Команда Next.js уже выпустила новое обновление для фреймворка, а также добавила несколько новых папок и функций в структуру проекта по умолчанию. Однако мы не будем подробно останавливаться на этом аспекте, так как он выходит за рамки данной статьи. Вы можете прочитать больше об обновлениях здесь , если хотите.

Во фрагменте ниже вы увидите базовую структуру файлов, с которыми мы будем взаимодействовать в этой статье.

└── pages /
    ├── forgot-password/
    │   └── [token]/
    │       └── [email].js
    ├── _app.js
    └── index.js

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

И поскольку реализация потока «сброса пароля» является нашей главной заботой, я думаю, будет лучше, если у нас будет меньше беспорядка. Теперь давайте немного познакомимся с этой структурой.

Вы заметите, что у нас есть forgot-passwordпапка в pagesкаталоге, которая содержит некоторые файлы. Но соглашение об именах этих файлов сильно отличается от того, как называются другие файлы. Имена файлов — token и email.js — заключаются в пару квадратных скобок.

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

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

В forgot-passwordпапке путь к пользовательскому интерфейсу, содержащему форму забытого пароля, можно получить здесь. Взгляните на это ниже.

http://localhost:3000/forgot-password/token/email

Поскольку это динамический маршрут, параметры tokenи emailURL всегда будут меняться в зависимости от пользователя, пытающегося сбросить свой пароль. Токен и электронная почта для пользователя A будут отличаться от таковых для пользователя B.

Чтение параметров URL с помощью хука Userouter 

Хук в Next.js useRouterможно использовать для реализации множества практических интерфейсных реализаций пользовательского интерфейса — от общей идеи реализации активного элемента панели навигации с .pathnameключом до более сложных функций.

Давайте теперь посмотрим, как мы можем читать параметры URL из динамических маршрутов с помощью useRouterхука, не так ли? Для этого вам нужно сначала импортировать модуль на свою страницу/компонент.

import { useRouter } from 'next/router'
export default function PageComponent({ children }) {
  const router = useRouter()
  return (
    <React.Fragment>
      {/* page content falls below */}
      <div>{children}</div>
    </React.Fragment>
  )
}

Фрагмент выше показывает основное использование хука. Поскольку нас интересуют параметры запроса URL, будет лучше, если мы деструктурируем queryметод хука вместо того, чтобы делать что-то вроде этого: router.query. Мы просто сделаем что-то подобное ниже.

import { useRouter } from 'next/router'
const { query } = useRouter()

Теперь мы можем приступить к созданию переменных, в которых будут храниться нужные нам параметры URL. Фрагмент ниже показывает, как вы можете это сделать.

const token = query.token
const email = query.email

Обратите внимание, что значения query.tokenи query.emailявляются результатом имени файлов. Напомним из структуры папок в forgot-passwordпапке, где у нас есть [email].jsи [token]файлы. Если вы переименуете эти файлы в [userEmail].jsи [userToken]соответственно, шаблон назначения этих переменных станет примерно таким, как показано ниже.

const token = query.userToken
const email = query.userEmail

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

Теперь, когда вы поняли, как эти параметры получаются из URL-адреса, давайте начнем с построения структуры форм.

Создание форм 

В этом разделе мы рассмотрим процесс создания формы и то, как вы можете использовать Axios для получения данных через произвольную конечную точку API. Мы не будем сосредотачиваться на стилизации этих форм и объяснении структуры. Я предполагаю, что вы уже знаете, как структурировать и стилизовать базовую форму React. Итак, давайте начнем с макета формы на маршруте «забыть пароль».

import React from 'react'
import axios from 'axios'
import { ErrModal, SuccessModal } from '../components/Modals'

export const DefaultResetPassword = () => {
  const [email, setEmail] = React.useState('')
  const [loading, setLoading] = React.useState(false)

  const handleForgot = () => { } // we’ll see this later

  return (
    <div>
      <form onSubmit={handleForgot} className="reset-password">
        <h1>Forgot Password</h1>
        <p>You are not alone. We’ve all been here at some point.</p>
        <div>
          <label htmlFor="email">Email address</label>
          <input
            type="email"
            name="email"
            id="email"
            placeholder= “your email address”
            value={email}
            onChange={(e) => setEmail(e.target.value)}
            required
          />
        </div>
        <button name="reset-pwd-button" className="reset-pwd">
          {!loading ? ‘Get secure link’: ‘Sending...’}
        </button>
      </form>
    </div>
  )
}

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

<p>You are not alone. We’ve all been here at some point</p>

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

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

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

import { authEndpoints } from '../endpoints'

export const DefaultResetPassword = () => {
  const handleForgot = async (e) => {
    e.preventDefault()
    try {
      setLoading(true)
      const response = await axios({
        method: 'POST',
        url: authEndpoints.recover,
        data: {
          email,
        },
        headers: {
          'Content-Type': 'application/json',
        },
      })
      setResestSuccess(response.data.msg)
      setLoading(false)
      setResetError('')
    } catch (error) {
      setLoading(false)
      const { data } = error.response
      setResetError(data.msg)
      setResestSuccess(null)
    }
  }
  return <div>{/* ...previous form component */}</div>
}

Из приведенного выше фрагмента вы заметите, что мы импортируем конечную точку API, на которую мы будем отправлять запрос POST, и именно поэтому мы передаем ее как переменную urlключу в методе Axios.

Запрос POST получает адрес электронной почты пользователя в качестве полезной нагрузки, которая, в свою очередь, будет проверена на сервере, и для этого адреса электронной почты будет создан JWT, который будет использоваться для авторизации процесса сброса пароля пользователя.

setResestSuccess(response.data.msg)
setLoading(false)
setResetError('')
catch (error) {
  setLoading(false)
  const { data } = error.response
  setResetError(data.msg)
  setResestSuccess(null)
}

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

Примером может служить setLoadingфункция, значение которой установлено trueв tryблоке. Затем его значение устанавливается равным false, когда данные были успешно отправлены. А если нет, у нас есть catchблок, который «отлавливает» ошибку и отображает сообщение об ошибке, которое мы деструктурировали из конечной точки.

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

Сеттеры получаются из объявления переменных состояния. См. их ниже.

import React from 'react'
import { ErrModal, SuccessModal } from '../components/Modals'

export const DefaultResetPassword = () => {
  const [resetSuccess, setResestSuccess] = React.useState()
  const [resetError, setResetError] = React.useState()
  return (
    <div>
      {resetError ? <ErrModal message={resetError} /> : null}
      {resetSuccess ? <SuccessModal message={resetSuccess} /> : null}
      <form onSubmit={handleForgot} className="reset-password">
        {/* form content */}
      </form>
    </div>
  )
}

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

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

export const SuccessModal = ({ message }) => {
  return (
    <div className="auth-success-msg">
      <p>{message}</p>
    </div>
  )
}
export const ErrModal = ({ message }) => {
  return (
    <div className="auth-err-msg">
      <p>{message}</p>
    </div>
  )
}

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

В дополнение ко всему, что было сказано, нам нужен способ проверки того, что правильный тип данных передается в качестве реквизита модальному компоненту. Этого можно добиться с помощью модуля «prop-type» в реакции.

propTypes.ErrModal = {
  message: propTypes.string.isRequired,
}
propTypes.SuccessModal = {
  message: propTypes.string.isRequired,
}

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

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

import axios from "axios";
import React from “react”;
import Head from “next/head”;
import { useRouter } from "next/router";
import { SuccessModal, ErrModal } from "../components/Modals";

const ResetPassword = () => {
  const [newPassword, setNewPassword] = React.useState("");
  const [loading, setLoading] = React.useState(false);
  const [resetPasswordSuccess, setResetPasswordSuccess] = React.useState();
  const [resetPasswordError, setResetPasswordError] = React.useState();

  const { query } = useRouter();
  const token = query.token;
  const email = query.email;

  const resetPassword = () => { } // coming in later...

  return (
    <React.Fragment>
      <Head>
        <title>Reset your password</title>
      </Head>
      <div>
        {email && token ? (
          <div className="auth-wrapper">
            {resetPasswordSuccess ? (
              <SuccessModal message={resetPasswordSuccess} />
            ) : (
              null
            )}
            {resetPasswordError ? (
              <ErrModal message={resetPasswordError} />
            ) : (
              null
            )}
            <form onSubmit={resetPassword} className="reset-password">
              <h1>Reset Password</h1>
              <p>Please enter your new password</p>
              <div>
                <label htmlFor="password">Password*</label>
                <input
                  name="password"
                  type="password"
                  id="password"
                  placeholder="enter new pasword"
                  value={newPassword}
                  onChange={(e) => setNewPassword(e.target.value)}
                />
              </input>
              <button
                name="reset-pwd-button"
                className="reset-pwd"
              >
                {!loading ? "Reset" : "Processing..."}
              </button>
            </form>
          </div>
        ) : (
          <p>The page you're trying to get to isn't available</p>
        )}
      </div>
    </React.Fragment>
  );
};

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

Вы можете видеть, как мы читаем параметры из URL-адреса, а также объявления ошибок сброса пароля и переменных успеха.

const [resetPasswordSuccess, setResetPasswordSuccess] = React.useState()
const [resetPasswordError, setResetPasswordError] = React.useState()
const { query } = useRouter()
const token = query.token
const email = query.email

Вы также заметите, как мы условно отображаем форму сброса пароля, проверяя , присутствуют ли переменные и в URL-адресе email; tokenесли эти переменные ложны (т. е. их нет в URL-адресе), мы отображаем текст, который говорит, что страница, которую они ищут, недоступна.

{
  email && token ? (
    <div className="auth-wrapper">
      <FormComponentt />
    </div>
  ) : (
    <p>The page you’re trying to get to isn’t available</p>
  )
}

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

import { authEndpoints } from '../endpoints'
const resetPassword = async (e) => {
  e.preventDefault()
  try {
    setLoading(true)
    const response = await axios({
      method: 'POST',
      url: authEndpoints.resetPassword,
      data: {
        token,
        email,
        password: newPassword,
      },
      headers: {
        'Content-Type': 'application/json',
      },
    })
    setResetPasswordSuccess(response.data.msg)
    setLoading(false)
    setTimeout(() => {
      router.push('/')
    }, 4000)
    setResetPasswordError('')
  } catch (error) {
    setLoading(false)
    setResetPasswordError(error.response.data.msg)
    setResetPasswordSuccess(null)
  }
}

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

setTimeout(() => {
  router.push('/')
}, 4000)

Когда вы посмотрите на приведенный выше фрагмент, вы увидите, как мы используем setTimeoutметод в JavaScript и хук Next.js useRouterдля перенаправления пользователя на домашнюю страницу — которая в данном случае является страницей входа — через четыре секунды. (вы можете сократить этот период времени, если хотите), чтобы они могли снова войти в систему.

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

Последние мысли 

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

Спасибо за чтение. Я надеюсь, что эта статья была полезной!

Оригинальный источник статьи на https://www.smashingmagazine.com

#nextjs #axios #react #security

What is GEEK

Buddha Community

Сброс пароля с помощью Axios, Next.js и useRouter Hook

NBB: Ad-hoc CLJS Scripting on Node.js

Nbb

Not babashka. Node.js babashka!?

Ad-hoc CLJS scripting on Node.js.

Status

Experimental. Please report issues here.

Goals and features

Nbb's main goal is to make it easy to get started with ad hoc CLJS scripting on Node.js.

Additional goals and features are:

  • Fast startup without relying on a custom version of Node.js.
  • Small artifact (current size is around 1.2MB).
  • First class macros.
  • Support building small TUI apps using Reagent.
  • Complement babashka with libraries from the Node.js ecosystem.

Requirements

Nbb requires Node.js v12 or newer.

How does this tool work?

CLJS code is evaluated through SCI, the same interpreter that powers babashka. Because SCI works with advanced compilation, the bundle size, especially when combined with other dependencies, is smaller than what you get with self-hosted CLJS. That makes startup faster. The trade-off is that execution is less performant and that only a subset of CLJS is available (e.g. no deftype, yet).

Usage

Install nbb from NPM:

$ npm install nbb -g

Omit -g for a local install.

Try out an expression:

$ nbb -e '(+ 1 2 3)'
6

And then install some other NPM libraries to use in the script. E.g.:

$ npm install csv-parse shelljs zx

Create a script which uses the NPM libraries:

(ns script
  (:require ["csv-parse/lib/sync$default" :as csv-parse]
            ["fs" :as fs]
            ["path" :as path]
            ["shelljs$default" :as sh]
            ["term-size$default" :as term-size]
            ["zx$default" :as zx]
            ["zx$fs" :as zxfs]
            [nbb.core :refer [*file*]]))

(prn (path/resolve "."))

(prn (term-size))

(println (count (str (fs/readFileSync *file*))))

(prn (sh/ls "."))

(prn (csv-parse "foo,bar"))

(prn (zxfs/existsSync *file*))

(zx/$ #js ["ls"])

Call the script:

$ nbb script.cljs
"/private/tmp/test-script"
#js {:columns 216, :rows 47}
510
#js ["node_modules" "package-lock.json" "package.json" "script.cljs"]
#js [#js ["foo" "bar"]]
true
$ ls
node_modules
package-lock.json
package.json
script.cljs

Macros

Nbb has first class support for macros: you can define them right inside your .cljs file, like you are used to from JVM Clojure. Consider the plet macro to make working with promises more palatable:

(defmacro plet
  [bindings & body]
  (let [binding-pairs (reverse (partition 2 bindings))
        body (cons 'do body)]
    (reduce (fn [body [sym expr]]
              (let [expr (list '.resolve 'js/Promise expr)]
                (list '.then expr (list 'clojure.core/fn (vector sym)
                                        body))))
            body
            binding-pairs)))

Using this macro we can look async code more like sync code. Consider this puppeteer example:

(-> (.launch puppeteer)
      (.then (fn [browser]
               (-> (.newPage browser)
                   (.then (fn [page]
                            (-> (.goto page "https://clojure.org")
                                (.then #(.screenshot page #js{:path "screenshot.png"}))
                                (.catch #(js/console.log %))
                                (.then #(.close browser)))))))))

Using plet this becomes:

(plet [browser (.launch puppeteer)
       page (.newPage browser)
       _ (.goto page "https://clojure.org")
       _ (-> (.screenshot page #js{:path "screenshot.png"})
             (.catch #(js/console.log %)))]
      (.close browser))

See the puppeteer example for the full code.

Since v0.0.36, nbb includes promesa which is a library to deal with promises. The above plet macro is similar to promesa.core/let.

Startup time

$ time nbb -e '(+ 1 2 3)'
6
nbb -e '(+ 1 2 3)'   0.17s  user 0.02s system 109% cpu 0.168 total

The baseline startup time for a script is about 170ms seconds on my laptop. When invoked via npx this adds another 300ms or so, so for faster startup, either use a globally installed nbb or use $(npm bin)/nbb script.cljs to bypass npx.

Dependencies

NPM dependencies

Nbb does not depend on any NPM dependencies. All NPM libraries loaded by a script are resolved relative to that script. When using the Reagent module, React is resolved in the same way as any other NPM library.

Classpath

To load .cljs files from local paths or dependencies, you can use the --classpath argument. The current dir is added to the classpath automatically. So if there is a file foo/bar.cljs relative to your current dir, then you can load it via (:require [foo.bar :as fb]). Note that nbb uses the same naming conventions for namespaces and directories as other Clojure tools: foo-bar in the namespace name becomes foo_bar in the directory name.

To load dependencies from the Clojure ecosystem, you can use the Clojure CLI or babashka to download them and produce a classpath:

$ classpath="$(clojure -A:nbb -Spath -Sdeps '{:aliases {:nbb {:replace-deps {com.github.seancorfield/honeysql {:git/tag "v2.0.0-rc5" :git/sha "01c3a55"}}}}}')"

and then feed it to the --classpath argument:

$ nbb --classpath "$classpath" -e "(require '[honey.sql :as sql]) (sql/format {:select :foo :from :bar :where [:= :baz 2]})"
["SELECT foo FROM bar WHERE baz = ?" 2]

Currently nbb only reads from directories, not jar files, so you are encouraged to use git libs. Support for .jar files will be added later.

Current file

The name of the file that is currently being executed is available via nbb.core/*file* or on the metadata of vars:

(ns foo
  (:require [nbb.core :refer [*file*]]))

(prn *file*) ;; "/private/tmp/foo.cljs"

(defn f [])
(prn (:file (meta #'f))) ;; "/private/tmp/foo.cljs"

Reagent

Nbb includes reagent.core which will be lazily loaded when required. You can use this together with ink to create a TUI application:

$ npm install ink

ink-demo.cljs:

(ns ink-demo
  (:require ["ink" :refer [render Text]]
            [reagent.core :as r]))

(defonce state (r/atom 0))

(doseq [n (range 1 11)]
  (js/setTimeout #(swap! state inc) (* n 500)))

(defn hello []
  [:> Text {:color "green"} "Hello, world! " @state])

(render (r/as-element [hello]))

Promesa

Working with callbacks and promises can become tedious. Since nbb v0.0.36 the promesa.core namespace is included with the let and do! macros. An example:

(ns prom
  (:require [promesa.core :as p]))

(defn sleep [ms]
  (js/Promise.
   (fn [resolve _]
     (js/setTimeout resolve ms))))

(defn do-stuff
  []
  (p/do!
   (println "Doing stuff which takes a while")
   (sleep 1000)
   1))

(p/let [a (do-stuff)
        b (inc a)
        c (do-stuff)
        d (+ b c)]
  (prn d))
$ nbb prom.cljs
Doing stuff which takes a while
Doing stuff which takes a while
3

Also see API docs.

Js-interop

Since nbb v0.0.75 applied-science/js-interop is available:

(ns example
  (:require [applied-science.js-interop :as j]))

(def o (j/lit {:a 1 :b 2 :c {:d 1}}))

(prn (j/select-keys o [:a :b])) ;; #js {:a 1, :b 2}
(prn (j/get-in o [:c :d])) ;; 1

Most of this library is supported in nbb, except the following:

  • destructuring using :syms
  • property access using .-x notation. In nbb, you must use keywords.

See the example of what is currently supported.

Examples

See the examples directory for small examples.

Also check out these projects built with nbb:

API

See API documentation.

Migrating to shadow-cljs

See this gist on how to convert an nbb script or project to shadow-cljs.

Build

Prequisites:

  • babashka >= 0.4.0
  • Clojure CLI >= 1.10.3.933
  • Node.js 16.5.0 (lower version may work, but this is the one I used to build)

To build:

  • Clone and cd into this repo
  • bb release

Run bb tasks for more project-related tasks.

Download Details:
Author: borkdude
Download Link: Download The Source Code
Official Website: https://github.com/borkdude/nbb 
License: EPL-1.0

#node #javascript

Сброс пароля с помощью Axios, Next.js и useRouter Hook

Узнайте, как реализовать функцию сброса пароля с помощью Axios, useRouter Hook и динамических маршрутов Next.js, отдавая приоритет UX.

Большинство пользователей, как правило, забывают свои пароли, поэтому создавайте возможности, которые всегда будут ценны для людей, использующих ваши продукты. В этой статье мы обсудим, как реализовать функцию сброса пароля с динамическими маршрутами Next.js, отдавая приоритет UX.

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

Хотя существует множество ресурсов, посвященных созданию аутентифицированных приложений с помощью Next.js. Ну, есть даже проект с открытым исходным кодом , который буквально обрабатывает аутентификацию с нуля.

Но рамки этой статьи не охватывают всю концепцию аутентификации. Мы выбираем только конкретный шаблон процесса аутентификации: поток «сброса пароля» и то, как его можно реализовать на стороне клиента — интерфейсе — веб-приложения.

В этой статье вы увидите, как эту функцию можно реализовать с помощью распространенного инструмента выборки данных Axios — встроенной функции динамических маршрутов Next.js и useRouterхука.

Обзор процедуры сброса пароля 

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

Есть такая популярная поговорка: «Пользователи всегда будут забывать свои пароли», и это абсолютная правда. Многие люди даже боятся страницы «сброс пароля», потому что, если подумать, после того, как они потратили много времени, пытаясь угадать свои пароли — все безрезультатно — они либо расстроены, либо злы, когда попадают на эту страницу. конкретная страница.

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

Общий поток процесса сброса пароля можно увидеть ниже.

  • Пользователь расстроен после безуспешной попытки подписать. Они нажимают на ссылку «сброс пароля» и перенаправляются на соответствующую страницу. Пользовательский интерфейс, который они видят, представляет собой типичную веб-форму, которая принимает либо их адрес электронной почты, либо имя пользователя.
  • Когда они вводят свой адрес электронной почты или имя пользователя в поле ввода, они нажимают кнопку с обычным текстом «отправьте мне ссылку для восстановления по электронной почте».
  • Они получают подтверждение того, что безопасная ссылка была отправлена ​​на их электронную почту. Иногда этот текст подтверждения может отображаться в карточном компоненте или в модальном окне, которое со временем исчезает.
     

Примечание . В целях безопасности и хорошего UX хорошо использовать текст, очень похожий на этот: «На ваш почтовый ящик отправлено электронное письмо. Пожалуйста, нажмите на ссылку, когда получите ее». Вы можете построить это предложение так, как считаете нужным, если оно не показывает, что введенный им адрес электронной почты или имя пользователя существует в базе данных. Такой подход не позволяет злоумышленникам узнать, существует ли вообще это электронное письмо, тем самым саботируя любые попытки фишинга, которые они могут предпринять с указанным адресом электронной почты. Что касается UX, текст не гарантирует пользователю, что введенные им учетные данные являются правильными. Это, в свою очередь, позволяет им дважды проверять любые учетные данные, которые они отправляют.

  • Ссылка, отправленная на их адрес электронной почты, содержит JWT и их user_idили, в данном случае, их адрес электронной почты.
  • При нажатии на эту ссылку они перенаправляются на маршрут/страницу, где они могут ввести свой новый пароль. Маршрут, по которому будет идти пользователь, может иметь форму ниже
https://localhost:3000/reset-password/user-email/JWToken
  • Последняя часть процесса — проверка того, связан ли созданный JWT с учетной записью пользователя. Если нет, мы выдаем ошибку, отображая сообщение об ошибке, полученное от бэкэнда.

Теперь, когда вы увидели, как устроен процесс «сброса пароля», давайте посмотрим, как его можно реализовать с помощью Next.js.

Понимание динамических маршрутов 

В этом разделе мы рассмотрим концепцию динамических маршрутов, проиллюстрировав ее структурой папок проекта Next.js, и посмотрим, как мы интегрируем ее в функцию «сброса пароля». Но сначала давайте настроим приложение Next.js.

npx create-next-app app-name

Приведенная выше команда делает это за нас. Команда Next.js уже выпустила новое обновление для фреймворка, а также добавила несколько новых папок и функций в структуру проекта по умолчанию. Однако мы не будем подробно останавливаться на этом аспекте, так как он выходит за рамки данной статьи. Вы можете прочитать больше об обновлениях здесь , если хотите.

Во фрагменте ниже вы увидите базовую структуру файлов, с которыми мы будем взаимодействовать в этой статье.

└── pages /
    ├── forgot-password/
    │   └── [token]/
    │       └── [email].js
    ├── _app.js
    └── index.js

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

И поскольку реализация потока «сброса пароля» является нашей главной заботой, я думаю, будет лучше, если у нас будет меньше беспорядка. Теперь давайте немного познакомимся с этой структурой.

Вы заметите, что у нас есть forgot-passwordпапка в pagesкаталоге, которая содержит некоторые файлы. Но соглашение об именах этих файлов сильно отличается от того, как называются другие файлы. Имена файлов — token и email.js — заключаются в пару квадратных скобок.

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

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

В forgot-passwordпапке путь к пользовательскому интерфейсу, содержащему форму забытого пароля, можно получить здесь. Взгляните на это ниже.

http://localhost:3000/forgot-password/token/email

Поскольку это динамический маршрут, параметры tokenи emailURL всегда будут меняться в зависимости от пользователя, пытающегося сбросить свой пароль. Токен и электронная почта для пользователя A будут отличаться от таковых для пользователя B.

Чтение параметров URL с помощью хука Userouter 

Хук в Next.js useRouterможно использовать для реализации множества практических интерфейсных реализаций пользовательского интерфейса — от общей идеи реализации активного элемента панели навигации с .pathnameключом до более сложных функций.

Давайте теперь посмотрим, как мы можем читать параметры URL из динамических маршрутов с помощью useRouterхука, не так ли? Для этого вам нужно сначала импортировать модуль на свою страницу/компонент.

import { useRouter } from 'next/router'
export default function PageComponent({ children }) {
  const router = useRouter()
  return (
    <React.Fragment>
      {/* page content falls below */}
      <div>{children}</div>
    </React.Fragment>
  )
}

Фрагмент выше показывает основное использование хука. Поскольку нас интересуют параметры запроса URL, будет лучше, если мы деструктурируем queryметод хука вместо того, чтобы делать что-то вроде этого: router.query. Мы просто сделаем что-то подобное ниже.

import { useRouter } from 'next/router'
const { query } = useRouter()

Теперь мы можем приступить к созданию переменных, в которых будут храниться нужные нам параметры URL. Фрагмент ниже показывает, как вы можете это сделать.

const token = query.token
const email = query.email

Обратите внимание, что значения query.tokenи query.emailявляются результатом имени файлов. Напомним из структуры папок в forgot-passwordпапке, где у нас есть [email].jsи [token]файлы. Если вы переименуете эти файлы в [userEmail].jsи [userToken]соответственно, шаблон назначения этих переменных станет примерно таким, как показано ниже.

const token = query.userToken
const email = query.userEmail

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

Теперь, когда вы поняли, как эти параметры получаются из URL-адреса, давайте начнем с построения структуры форм.

Создание форм 

В этом разделе мы рассмотрим процесс создания формы и то, как вы можете использовать Axios для получения данных через произвольную конечную точку API. Мы не будем сосредотачиваться на стилизации этих форм и объяснении структуры. Я предполагаю, что вы уже знаете, как структурировать и стилизовать базовую форму React. Итак, давайте начнем с макета формы на маршруте «забыть пароль».

import React from 'react'
import axios from 'axios'
import { ErrModal, SuccessModal } from '../components/Modals'

export const DefaultResetPassword = () => {
  const [email, setEmail] = React.useState('')
  const [loading, setLoading] = React.useState(false)

  const handleForgot = () => { } // we’ll see this later

  return (
    <div>
      <form onSubmit={handleForgot} className="reset-password">
        <h1>Forgot Password</h1>
        <p>You are not alone. We’ve all been here at some point.</p>
        <div>
          <label htmlFor="email">Email address</label>
          <input
            type="email"
            name="email"
            id="email"
            placeholder= “your email address”
            value={email}
            onChange={(e) => setEmail(e.target.value)}
            required
          />
        </div>
        <button name="reset-pwd-button" className="reset-pwd">
          {!loading ? ‘Get secure link’: ‘Sending...’}
        </button>
      </form>
    </div>
  )
}

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

<p>You are not alone. We’ve all been here at some point</p>

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

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

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

import { authEndpoints } from '../endpoints'

export const DefaultResetPassword = () => {
  const handleForgot = async (e) => {
    e.preventDefault()
    try {
      setLoading(true)
      const response = await axios({
        method: 'POST',
        url: authEndpoints.recover,
        data: {
          email,
        },
        headers: {
          'Content-Type': 'application/json',
        },
      })
      setResestSuccess(response.data.msg)
      setLoading(false)
      setResetError('')
    } catch (error) {
      setLoading(false)
      const { data } = error.response
      setResetError(data.msg)
      setResestSuccess(null)
    }
  }
  return <div>{/* ...previous form component */}</div>
}

Из приведенного выше фрагмента вы заметите, что мы импортируем конечную точку API, на которую мы будем отправлять запрос POST, и именно поэтому мы передаем ее как переменную urlключу в методе Axios.

Запрос POST получает адрес электронной почты пользователя в качестве полезной нагрузки, которая, в свою очередь, будет проверена на сервере, и для этого адреса электронной почты будет создан JWT, который будет использоваться для авторизации процесса сброса пароля пользователя.

setResestSuccess(response.data.msg)
setLoading(false)
setResetError('')
catch (error) {
  setLoading(false)
  const { data } = error.response
  setResetError(data.msg)
  setResestSuccess(null)
}

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

Примером может служить setLoadingфункция, значение которой установлено trueв tryблоке. Затем его значение устанавливается равным false, когда данные были успешно отправлены. А если нет, у нас есть catchблок, который «отлавливает» ошибку и отображает сообщение об ошибке, которое мы деструктурировали из конечной точки.

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

Сеттеры получаются из объявления переменных состояния. См. их ниже.

import React from 'react'
import { ErrModal, SuccessModal } from '../components/Modals'

export const DefaultResetPassword = () => {
  const [resetSuccess, setResestSuccess] = React.useState()
  const [resetError, setResetError] = React.useState()
  return (
    <div>
      {resetError ? <ErrModal message={resetError} /> : null}
      {resetSuccess ? <SuccessModal message={resetSuccess} /> : null}
      <form onSubmit={handleForgot} className="reset-password">
        {/* form content */}
      </form>
    </div>
  )
}

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

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

export const SuccessModal = ({ message }) => {
  return (
    <div className="auth-success-msg">
      <p>{message}</p>
    </div>
  )
}
export const ErrModal = ({ message }) => {
  return (
    <div className="auth-err-msg">
      <p>{message}</p>
    </div>
  )
}

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

В дополнение ко всему, что было сказано, нам нужен способ проверки того, что правильный тип данных передается в качестве реквизита модальному компоненту. Этого можно добиться с помощью модуля «prop-type» в реакции.

propTypes.ErrModal = {
  message: propTypes.string.isRequired,
}
propTypes.SuccessModal = {
  message: propTypes.string.isRequired,
}

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

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

import axios from "axios";
import React from “react”;
import Head from “next/head”;
import { useRouter } from "next/router";
import { SuccessModal, ErrModal } from "../components/Modals";

const ResetPassword = () => {
  const [newPassword, setNewPassword] = React.useState("");
  const [loading, setLoading] = React.useState(false);
  const [resetPasswordSuccess, setResetPasswordSuccess] = React.useState();
  const [resetPasswordError, setResetPasswordError] = React.useState();

  const { query } = useRouter();
  const token = query.token;
  const email = query.email;

  const resetPassword = () => { } // coming in later...

  return (
    <React.Fragment>
      <Head>
        <title>Reset your password</title>
      </Head>
      <div>
        {email && token ? (
          <div className="auth-wrapper">
            {resetPasswordSuccess ? (
              <SuccessModal message={resetPasswordSuccess} />
            ) : (
              null
            )}
            {resetPasswordError ? (
              <ErrModal message={resetPasswordError} />
            ) : (
              null
            )}
            <form onSubmit={resetPassword} className="reset-password">
              <h1>Reset Password</h1>
              <p>Please enter your new password</p>
              <div>
                <label htmlFor="password">Password*</label>
                <input
                  name="password"
                  type="password"
                  id="password"
                  placeholder="enter new pasword"
                  value={newPassword}
                  onChange={(e) => setNewPassword(e.target.value)}
                />
              </input>
              <button
                name="reset-pwd-button"
                className="reset-pwd"
              >
                {!loading ? "Reset" : "Processing..."}
              </button>
            </form>
          </div>
        ) : (
          <p>The page you're trying to get to isn't available</p>
        )}
      </div>
    </React.Fragment>
  );
};

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

Вы можете видеть, как мы читаем параметры из URL-адреса, а также объявления ошибок сброса пароля и переменных успеха.

const [resetPasswordSuccess, setResetPasswordSuccess] = React.useState()
const [resetPasswordError, setResetPasswordError] = React.useState()
const { query } = useRouter()
const token = query.token
const email = query.email

Вы также заметите, как мы условно отображаем форму сброса пароля, проверяя , присутствуют ли переменные и в URL-адресе email; tokenесли эти переменные ложны (т. е. их нет в URL-адресе), мы отображаем текст, который говорит, что страница, которую они ищут, недоступна.

{
  email && token ? (
    <div className="auth-wrapper">
      <FormComponentt />
    </div>
  ) : (
    <p>The page you’re trying to get to isn’t available</p>
  )
}

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

import { authEndpoints } from '../endpoints'
const resetPassword = async (e) => {
  e.preventDefault()
  try {
    setLoading(true)
    const response = await axios({
      method: 'POST',
      url: authEndpoints.resetPassword,
      data: {
        token,
        email,
        password: newPassword,
      },
      headers: {
        'Content-Type': 'application/json',
      },
    })
    setResetPasswordSuccess(response.data.msg)
    setLoading(false)
    setTimeout(() => {
      router.push('/')
    }, 4000)
    setResetPasswordError('')
  } catch (error) {
    setLoading(false)
    setResetPasswordError(error.response.data.msg)
    setResetPasswordSuccess(null)
  }
}

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

setTimeout(() => {
  router.push('/')
}, 4000)

Когда вы посмотрите на приведенный выше фрагмент, вы увидите, как мы используем setTimeoutметод в JavaScript и хук Next.js useRouterдля перенаправления пользователя на домашнюю страницу — которая в данном случае является страницей входа — через четыре секунды. (вы можете сократить этот период времени, если хотите), чтобы они могли снова войти в систему.

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

Последние мысли 

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

Спасибо за чтение. Я надеюсь, что эта статья была полезной!

Оригинальный источник статьи на https://www.smashingmagazine.com

#nextjs #axios #react #security

Eva  Murphy

Eva Murphy

1625674200

Google analytics Setup with Next JS, React JS using Router Events - 14

In this video, we are going to implement Google Analytics to our Next JS application. Tracking page views of an application is very important.

Google analytics will allow us to track analytics information.

Frontend: https://github.com/amitavroy/video-reviews
API: https://github.com/amitavdevzone/video-review-api
App link: https://video-reviews.vercel.app

You can find me on:
Twitter: https://twitter.com/amitavroy7​
Discord: https://discord.gg/Em4nuvQk

#next js #js #react js #react #next #google analytics

What are hooks in React JS? - INFO AT ONE

In this article, you will learn what are hooks in React JS? and when to use react hooks? React JS is developed by Facebook in the year 2013. There are many students and the new developers who have confusion between react and hooks in react. Well, it is not different, react is a programming language and hooks is a function which is used in react programming language.
Read More:- https://infoatone.com/what-are-hooks-in-react-js/

#react #hooks in react #react hooks example #react js projects for beginners #what are hooks in react js? #when to use react hooks

Teresa  Bosco

Teresa Bosco

1598769720

Vue js Axios Tutorial

Vue js Axios Tutorial is today’s leading topic.  Axios is an excellent http client library. It uses promises by default and runs on both the client and the server. Axios is an impressive HTTP client library which lets you asynchronously issue HTTP requests to interact with REST endpoints. Consume REST APIs and make HTTP Requests is easy with Axios and Vue.js.

Vue js Axios Tutorial

We will use Axios to send an HTTP request to the Node.js server and fetch the data from the database and display it on the client side. We will make GET and POST request to the Node.js server. At the backend, we will use Express web framework. Okay, so first we need to install Vue.js using CLI. If you are not familiar with Vue cli, then please check out this Vue cli Tutorial.

#axios and vue.js #axios #vue cli #node.js #vue js