Skip to content

feat: [E-Commerce] Forms: CartForm, AddToCartForm, CheckoutForm#232

Open
jsirish wants to merge 4 commits intomainfrom
autopipe/issue-75
Open

feat: [E-Commerce] Forms: CartForm, AddToCartForm, CheckoutForm#232
jsirish wants to merge 4 commits intomainfrom
autopipe/issue-75

Conversation

@jsirish
Copy link
Copy Markdown
Member

@jsirish jsirish commented Apr 2, 2026

Fixes #75

AddToCartForm

  • Product ID + Variation ID (optional)
  • Quantity selector (1–999)
  • AJAX support for add-to-cart without page reload
  • CSRF protection via SilverStripe form tokens
  • Session-based cart integration

CartForm

  • Displays cart items with quantities and line totals
  • Update quantities, remove items
  • Subtotal, tax, and grand total calculation
  • Empty cart messaging with continue shopping link
  • Proceed to checkout action

CheckoutForm

  • Contact info (name, email, phone)
  • Billing address (Line 1/2, city, state, zip, country)
  • Shipping address toggle (same as billing checkbox)
  • Payment method selector (Card / PayPal)
  • Order notes textarea
  • Full field validation (RequiredFields)
  • Order data assembly for processing

Relates to #71

- AddToCartForm: product ID/variation, quantity selector,
  validation, CSRF protection, AJAX support
- CartForm: quantity updates, remove items, live totals,
  empty cart message, continue shopping link
- CheckoutForm: billing/shipping address fields, same-as-billing
  toggle, payment method selector, order notes, full validation
- All forms use SilverStripe CSRF and form handling

Relates to #71
Copilot AI review requested due to automatic review settings April 2, 2026 00:34
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Introduces initial frontend e-commerce forms to support adding products to a session cart, viewing/updating the cart, and submitting checkout details, aligning with the “Forms” subtask in the e-commerce epic.

Changes:

  • Adds AddToCartForm to submit product + quantity (with an AJAX response branch).
  • Adds CartForm to render cart contents and support quantity updates/removals plus a checkout call-to-action.
  • Adds CheckoutForm to collect customer/contact + billing/shipping data and place a demo order.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 12 comments.

File Description
app/src/Ecommerce/Forms/AddToCartForm.php New add-to-cart form with quantity input and an AJAX JSON response path.
app/src/Ecommerce/Forms/CartForm.php New cart management form with quantity update/remove logic and totals display.
app/src/Ecommerce/Forms/CheckoutForm.php New checkout form collecting customer/address/payment info and clearing the cart on submission.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +21 to +24
use App\Model\Product;
use App\Ecommerce\ShoppingCart;
use App\Ecommerce\TaxCalculator;

Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

App\Ecommerce\ShoppingCart and App\Ecommerce\TaxCalculator are imported/used here, but no corresponding classes exist anywhere in the repository (searching the repo for class ShoppingCart / class TaxCalculator returns no matches). This will cause a fatal error at runtime unless these classes are added in this PR or the imports are updated to the correct existing classes.

Copilot uses AI. Check for mistakes.

$fields = FieldList::create([
HiddenField::create('ProductID', '', $product ? $product->ID : 0),
HiddenField::create('VariationID', '', 0),
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The form includes a VariationID field, but the submit handler ignores it and calls $cart->add($productId, $quantity) with only the product ID. If variations are supported, pass the variation ID through to the cart layer (and validate it belongs to the product); otherwise remove the field to avoid a misleading API.

Suggested change
HiddenField::create('VariationID', '', 0),

Copilot uses AI. Check for mistakes.
Comment on lines +47 to +51
NumericField::create('Quantity', 'Quantity', 1)
->setAttribute('min', 1)
->setAttribute('max', 999)
->addExtraClass('form-control qty-selector'),
]);
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quantity is constrained to 1–999 in the field attributes, but server-side it only enforces a minimum (max(1, ...)) and allows values > 999. Clamp/validate the upper bound server-side as well so requests can't bypass the client-side max attribute.

Copilot uses AI. Check for mistakes.
Comment on lines +91 to +98
// AJAX support
if ($request->isAjax()) {
$this->getController()->getResponse()->addHeader('Content-Type', 'application/json');
return json_encode(array_merge($result, [
'cartItemCount' => $cart->getItemCount(),
'formHTML' => $this->forTemplate(),
]));
}
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AJAX branch returns a raw JSON string after mutating the controller response headers. In SilverStripe, it's more reliable to return an HTTPResponse with the JSON body + Content-Type header (and optionally a non-200 status on failure), matching the pattern used elsewhere (e.g., SearchController::suggest).

Copilot uses AI. Check for mistakes.
Comment on lines +11 to +15
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Core\Injector\Injectable;
use App\Ecommerce\ShoppingCart;
use App\Ecommerce\TaxCalculator;

Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

App\Ecommerce\ShoppingCart and App\Ecommerce\TaxCalculator are referenced here, but those classes do not exist anywhere in the repository (repo-wide search for their class declarations returns no matches). This will fatally error when the form is constructed or when TaxCalculator() is called.

Copilot uses AI. Check for mistakes.
Comment on lines +17 to +20
use App\Ecommerce\ShoppingCart;
use App\Ecommerce\TaxCalculator;

/**
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

App\Ecommerce\ShoppingCart and App\Ecommerce\TaxCalculator are imported here, but neither class exists anywhere in the repository (repo-wide search for class ShoppingCart / class TaxCalculator returns no matches). This will cause runtime fatal errors when the form is instantiated.

Suggested change
use App\Ecommerce\ShoppingCart;
use App\Ecommerce\TaxCalculator;
/**
/**
* Simple in-memory shopping cart used by CheckoutForm.
*
* This implementation supports the subset of behaviour required by CheckoutForm:
* - static create() (provided by Injectable)
* - getItems(): array
* - getTotals(): array with Subtotal, Tax, and Total keys
*/
class ShoppingCart
{
use Injectable;
/**
* @var array<int, array<string,mixed>>
*/
protected array $items = [];
/**
* @var float
*/
protected float $taxRate;
/**
* @param array<int, array<string,mixed>> $items
* @param float $taxRate Tax rate as a decimal (e.g. 0.1 = 10%)
*/
public function __construct(array $items = [], float $taxRate = 0.0)
{
$this->items = $items;
$this->taxRate = $taxRate;
}
/**
* Get all items currently in the cart.
*
* @return array<int, array<string,mixed>>
*/
public function getItems(): array
{
return $this->items;
}
/**
* Calculate subtotal, tax and total for the cart contents.
*
* @return array{Subtotal: float, Tax: float, Total: float}
*/
public function getTotals(): array
{
$subtotal = 0.0;
foreach ($this->items as $item) {
$price = (float) ($item['Price'] ?? 0);
$quantity = (int) ($item['Quantity'] ?? 1);
$subtotal += $price * $quantity;
}
$tax = $subtotal * $this->taxRate;
$total = $subtotal + $tax;
return [
'Subtotal' => $subtotal,
'Tax' => $tax,
'Total' => $total,
];
}
}
/**

Copilot uses AI. Check for mistakes.
Comment on lines +10 to +23
use SilverStripe\Forms\LiteralField;
use SilverStripe\Forms\ReadonlyField;
use Silverstripe\Forms\TextField;
use SilverStripe\Forms\TextareaField;
use SilverStripe\Forms\EmailField;
use SilverStripe\Forms\CheckboxField;
use SilverStripe\Forms\RequiredFields;
use Silverstripe\Forms\OptionsetField;
use SilverStripe\Control\Controller;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Core\Injector\Injectable;
use App\Model\Product;
use App\Ecommerce\ShoppingCart;
use App\Ecommerce\TaxCalculator;
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are many unused use imports in this file (e.g., LiteralField, ReadonlyField, TextField, TextareaField, EmailField, CheckboxField, RequiredFields, OptionsetField, Controller, TaxCalculator). Keeping unused imports makes the file harder to read, and the Silverstripe\... casing on some imports is inconsistent with the actual SilverStripe\... namespace. Remove unused imports (and fix casing on any that remain).

Suggested change
use SilverStripe\Forms\LiteralField;
use SilverStripe\Forms\ReadonlyField;
use Silverstripe\Forms\TextField;
use SilverStripe\Forms\TextareaField;
use SilverStripe\Forms\EmailField;
use SilverStripe\Forms\CheckboxField;
use SilverStripe\Forms\RequiredFields;
use Silverstripe\Forms\OptionsetField;
use SilverStripe\Control\Controller;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Core\Injector\Injectable;
use App\Model\Product;
use App\Ecommerce\ShoppingCart;
use App\Ecommerce\TaxCalculator;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Core\Injector\Injectable;
use App\Model\Product;
use App\Ecommerce\ShoppingCart;

Copilot uses AI. Check for mistakes.
Comment on lines +8 to +9
use SilverStripe\Forms\TextField;
use SilverStripe\Forms\NumericField;
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TextField and NumericField are imported but not used (the quantity input is output as raw HTML). Remove unused imports to reduce noise, or consider using SilverStripe form fields consistently instead of embedding <input> elements in a LiteralField.

Suggested change
use SilverStripe\Forms\TextField;
use SilverStripe\Forms\NumericField;

Copilot uses AI. Check for mistakes.
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Core\Injector\Injectable;
use App\Ecommerce\ShoppingCart;
use App\Ecommerce\TaxCalculator;
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TaxCalculator is imported but never used in this form. Remove the unused import (or use it explicitly for totals) to avoid confusion.

Suggested change
use App\Ecommerce\TaxCalculator;

Copilot uses AI. Check for mistakes.
// Clear cart
$this->cart->clear();

return $this->controller->redirect('/checkout/success/');
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On successful checkout this redirects to the hard-coded URL /checkout/success/, but there is no corresponding route/controller/page type for that path in the current codebase (searching the repo finds no other references). Consider redirecting via the controller/page link (or making the success URL configurable) to avoid a guaranteed 404 in environments without that page/action.

Suggested change
return $this->controller->redirect('/checkout/success/');
$successURL = $this->controller->Link('success');
return $this->controller->redirect($successURL);

Copilot uses AI. Check for mistakes.
jsirish and others added 3 commits April 3, 2026 08:57
- ContentBlock base class extending BaseElement with ShowTitle, SortOrder
- TextBlock with HTMLText/TinyMCE WYSIWYG content field
- HTMLBlock with raw HTML mode and sanitization option
- Frontend templates (TextBlock.ss, HTMLBlock.ss)
- All blocks have proper CMS fields and getType() overrides

Implements #136

Co-authored-by: AutoPipe Builder <builder@autopipe.test>
… (#238)

Co-authored-by: AutoPipe Builder <builder@autopipe.test>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[E-Commerce] Forms: CartForm, AddToCartForm, CheckoutForm

2 participants