Добавляем форму заказа

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

Форма заказа

Для начала добавим пару компонентов в наш файл: сначала компонент поля формы. У этого компонента будет 2 свойства: имя поля (name) и его заголовок (label)

function FormInput(props: {name: string, label: string}) {
  return <div class="mb-3">
    <label>{props.label}</label>
    <input name={props.name} required class={'form-control'}/>
  </div>
}

А потом объявим компонент самой формы, который состоит из скрытого поля с id картины, трех полей данных пользователя и кноки "Оформить заказ"

function OrderForm(props: {itemId: string}) {
  return <form class={"border-top mt-3 pt-3"}>
    <input type='hidden' name='itemId' value={item.id}/>
    <FormInput name="name" label="Ваше имя"/>
    <FormInput name="email" label="Email"/>
    <FormInput name="phone" label="Телефона"/>
    <button class={"btn btn-success btn-lg w-100 mt-3"} type='submit'>
      Оформить заказ
    </button>
  </form>
}

Найдем код карточки товара и вставим тег <OrderForm/>, передав в него id нашего товара

<OrderForm itemId={item.id}/>

Вставим форму в код

Теперь нам нужно указать, куда эта форма будет отправлять данные.

Таблица заказов

Для начала, создадим таблицу, в которую будем складывать заказы. Сделаем ее, как и в случае каталога, с помощью метода Heap.Table

const orderTable = Heap.Table('order', {
  item: Heap.RefLink(catalogTable), // ссылка на картину
  itemTitle: Heap.String(), // название картины
  name: Heap.String(),
  phone: Heap.String(),
  email: Heap.String(),
})

Помимо имени, телефона и email покупателя, в таблице есть еще 2 поля:

  • item - ссылка RefLink, с указанием на таблицу картин
  • itemTitle - строка String название картины

Обработчик формы

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

Для этого объявим в нашем файле обработчик формы с помощью метода app.post и присвоим его в константу buyAction

const buyAction = app.post('/buy', async(ctx,req) => {
  // Получаем картину из таблицы
  const item = await catalogTable.getById(ctx, req.body.itemId !)

  // Создаем запись заказа
  const order = await orderTable.create(ctx,{
    item: item,
    itemTitle: item.title,
    name: req.body.name,
    email: req.body.email,
    phone: req.body.phone,
  })
})

Обработчик формы состоит из двух основных действий:

  • получаем картину из таблицы с помощью метода getById таблицы картин
  • добавляем строчку заказа, с помощью метода create таблицы заказов

Обратите внимание, что перед обоими методами мы используем ключевое слово await, поскольку работаем с внешним ресурсом (базой данных)

В значениях полей мы используем объект req.body, в который придет все, что передаст форма. Нам осталось только связать это действие с самой формой. Для этого, в компоненте OrderForm мы чуть поменяем первую строчку

function OrderForm(props: {itemId: string}) {
  return <form action={buyAction.url()} method='post' class={"border-top mt-3 pt-3"}>

Мы добавили указание метода method='post' (оно соответствует методу нашего обработчика app.post), и ссылку на обработчик с помощью свойства формы action={buyAction.url()}

Итоговый код

import {jsx} from '@app/html-jsx'
import {Heap} from '@app/heap'

const catalogTable = Heap.Table('pictures', {
  title: Heap.String(),
  material: Heap.Optional(Heap.String()),
  dimensions: Heap.Optional(Heap.String()),
  image: Heap.Optional( Heap.ImageFile() ),
  price: Heap.Optional(Heap.Number()),
})

export type CatalogItem = typeof catalogTable.T

const orderTable = Heap.Table('order', {
  item: Heap.RefLink(catalogTable),
  itemTitle: Heap.String(),
  name: Heap.String(),
  phone: Heap.String(),
  email: Heap.String(),
})

let currency = new Intl.NumberFormat('ru-RU', {
  style: 'currency',
  currency: 'RUB',
  maximumFractionDigits:0,
});

const mainScreen = app.html('/', async(ctx,req) => {

  const items =  await catalogTable.findAll(ctx)

  return <Layout title="Каталог картин">
    <h1 class={"fs-1 fs-header fs-bold mb-5"}>Каталог картин</h1>
    
    <div style={"display: flex; flex-direction: row; gap: 20px; flex-wrap: wrap;"}>
      {items.map( item =>
        <div style={"flex: 1; min-width: 250px;"}>
          <a href={cardScreen({id: item.id}).url()} class={"text-black text-decoration-none"}>
            <img src={item.image?.getThumbnailUrl(500)} class={"mw-100"}/>
            <div class='card-body p-2'>
              <h3>{item.title}</h3>
              <div>{item.material}</div>
              <div>{item.dimensions}</div>
            </div>
          </a>
        </div>
      )}
    </div>
  </Layout>
}) 

function FormInput(props: {name: string, label: string}) {
  return <div class="mb-3">
    <label>{props.label}</label>
    <input name={props.name} required class={'form-control'}/>
  </div>
}
function OrderForm(props: {itemId: string}) {
  return <form method='post' action={buyAction.url()}  class={"border-top mt-3 pt-3"}>
    <input type='hidden' name='itemId' value={props.itemId}/>
    <FormInput name="name" label="Ваше имя"/>
    <FormInput name="email" label="Email"/>
    <FormInput name="phone" label="Телефона"/>
    <button class={"btn btn-success btn-lg w-100 mt-3"} type='submit'>
      Оформить заказ
    </button>
  </form>
}

export const cardScreen = app.html('/card/:id', async(ctx,req) => {
  const item = await catalogTable.getById(ctx, req.params.id !)
  
  return <Layout title={item.title}>
    <div class={"row g-10"}>
      <div class={"col-md-8 col-12"}>
        <img class="mw-100" src={item.image?.getThumbnailUrl(900)}/>
      </div>
      <div class={"col-md-4 col-12"}>
        <h1 class={"fs-1 fs-header fw-bold"}>{item.title}</h1>
        <div>{item.material}</div>
        <div>{item.dimensions}</div>
        { item.price && <div class={"fs-2"}>{currency.format(item.price)}</div> }
        
        <OrderForm itemId={item.id}/>
      </div>
    </div>
  </Layout>
})

const buyAction = app.post('/buy', async(ctx,req) => {
  // Получаем картину из таблицы
  const item = await catalogTable.getById(ctx, req.body.itemId !)
  
  // Создаем запись заказа
  const order = await orderTable.create(ctx, {
    item: item,
    itemTitle: item.title,
    name: req.body.name,
    email: req.body.email,
    phone: req.body.phone,
  })
})

export function Layout( props: {title:string}, ...children: any) {
  
  return <html>
    <head>
      <title>{props.title}</title>
      <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/css/bootstrap.min.css" integrity="sha512-jnSuA4Ss2PkkikSOLtYs8BlYIeeIK1h99ty4YfvRPAlzr377vr3CXDb7sb7eEEBYjDtcYj+AjBH3FLv5uSJuXg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
      <meta charset="utf-8"/>
      <meta name="viewport" content="width=device-width, initial-scale=1"></meta>
    </head>

    <body>
      <div class={'container py-2 py-md-5'}>
        <div class={"col-md-8 col-12 mx-md-auto"}>
          <div class={"mb-5 border-bottom pb-2"}>
            <div class="d-flex flex-row justify-content-between">
              <div class="d-flex flex-row">
                <img class="me-3" height='30' width='30' src='https://chatium.com/s/static/img/logos/logo_round.png'/>
                <div class={"p-1 me-3"}>
                  <a href={mainScreen.url()}>
                    Каталог картин
                  </a>
                </div>
                <div class={"p-1 me-3 d-none d-md-block"}>
                  <a href={'https://play.chatium.com/s/ide/pl/tutorial/catalog/backend.tsx'}>
                    Код
                  </a>
                </div>
                <div class={"p-1 me-3 d-none d-md-block"}>
                  <a href={'https://chatium.ru/docs/start'}>
                    Документация
                  </a>
                </div>
              </div>
            </div>
          </div>
          {children}
        </div>
      </div>
    </body>
  </html>
}

Следующий шаг: Добавляем страницу "Спасибо за заказ"

❤️ Made with love on Chatium

ООО "Чатиум"

Информация о компании