ChatiumFor developersPlaygroundPricing
Sign in

Adding an Order Form

Now let's add a feature to the product card that allows people to place orders: a form and a handler for this form.

Order Form

First, we will add a couple of components to our file: starting with the form field component. This component will have two properties: the name of the field (name) and its label (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>
}

Next, we will declare the order form component, which consists of a hidden field with the item ID, three fields for user data, and a button labeled "Place Order".

function OrderForm(props: {itemId: string}) {
  return <form class={"border-top mt-3 pt-3"}>
    <input type='hidden' name='itemId' value={props.itemId}/>
    <FormInput name="name" label="Your Name"/>
    <FormInput name="email" label="Email"/>
    <FormInput name="phone" label="Phone"/>
    <button class={"btn btn-success btn-lg w-100 mt-3"} type='submit'>
      Place Order
    </button>
  </form>
}

We will find the product card code and insert the <OrderForm/> tag, passing in the ID of our product.

<OrderForm itemId={item.id}/>

Insert Form into Code

Now we need to specify where this form will send its data.

Orders Table

First, let's create a table where we will store the orders. We will do this, as we did with the catalog, using the method Heap.Table.

const orderTable = Heap.Table('order', {
  item: Heap.RefLink(catalogTable), // reference to the painting
  itemTitle: Heap.String(), // title of the painting
  name: Heap.String(),
  phone: Heap.String(),
  email: Heap.String(),
})

In addition to the buyer's name, phone, and email, the table has two more fields:

  • item - a reference RefLink pointing to the paintings table
  • itemTitle - a string String containing the title of the painting

Form Handler

Now we need to ensure that when the form is submitted, an order is created in the orders table, and the user sees a message that their order has been accepted.

To do this, we will declare a form handler in our file using the method app.post and assign it to the constant buyAction.

const buyAction = app.post('/buy', async(ctx,req) => {
  // Get the painting from the table
  const item = await catalogTable.getById(ctx, req.body.itemId!)

  // Create an order record
  const order = await orderTable.create(ctx,{
    item: item,
    itemTitle: item.title,
    name: req.body.name,
    email: req.body.email,
    phone: req.body.phone,
  })
})

The form handler consists of two main actions:

  • retrieving the painting from the table using the method getById of the paintings table
  • adding an order entry using the method create of the orders table

Note that we use the await keyword before both methods since we are working with an external resource (the database).

In the field values, we use the req.body object, which will contain everything the form sends. We just need to link this action to the form itself. For this, in the OrderForm component, we will slightly change the first line.

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

We added the method specification method='post' (which corresponds to our handler method app.post), and a link to the handler using the form property action={buyAction.url()}.

Final Code

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="Catalog of Paintings">
    <h1 class={"fs-1 fs-header fs-bold mb-5"}>Catalog of Paintings</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="Your Name"/>
    <FormInput name="email" label="Email"/>
    <FormInput name="phone" label="Phone"/>
    <button class={"btn btn-success btn-lg w-100 mt-3"} type='submit'>
      Place Order
    </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) => {
  // Get the painting from the table
  const item = await catalogTable.getById(ctx, req.body.itemId!)
  
  // Create an order record
  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()}>
                    Catalog of Paintings
                  </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'}>
                    Code
                  </a>
                </div>
                <div class={"p-1 me-3 d-none d-md-block"}>
                  <a href={'https://chatium.ru/docs/start'}>
                    Documentation
                  </a>
                </div>
              </div>
            </div>
          </div>
          {children}
        </div>
      </div>
    </body>
  </html>
}

Next Step: Adding a "Thank You for Your Order" Page