Implemented phase 3 for the POC prototype in the Website service.

This commit is contained in:
javier committed 2026-09-21 12:11:25 +02:00
1 parent b1ceed7790
commit 0dc3fe6bea
29 files changed
+4089 -66

No files matched your search

@@ -0,0 +1,75 @@
import Elementary
import Persistence
/// The seller's answer to an open offer: accept or decline, each a form.
struct AnswerOffer: HTML {
// MARK: Properties
/// The offer answered.
let offer: Offer
/// The path to return to afterwards; accepting leads to the sale record instead.
let returnPath: String
// MARK: Computed
var body: some HTML {
form(
.class("answer"),
.action(.Route.answer(offer: offer.id)),
.method(.post)
) {
input(
.type(.hidden),
.name("return"),
.value(returnPath)
)
button(
.class("button button--primary button--sm"),
.type(.submit),
.name("answer"),
.custom(name: "value", value: "accept")
) {
"Accept"
}
button(
.class("button button--sm"),
.type(.submit),
.name("answer"),
.custom(name: "value", value: "decline")
) {
"Decline"
}
}
}
}
// MARK: - Extensions
extension Offer.Status {
// MARK: Computed
/// The status in a word.
var label: String {
switch self {
case .open: "Open"
case .accepted: "Accepted"
case .declined: "Declined"
case .expired: "Expired"
}
}
/// The chip colouring for the status.
var tone: Chip.Tone {
switch self {
case .open: .warn
case .accepted: .good
case .declined: .bad
case .expired: .neutral
}
}
}
@@ -0,0 +1,68 @@
import Elementary
import Persistence
/// The five steps of a sale's handshake, as dots: done, current, or still to come.
struct Handshake: HTML {
// MARK: Properties
/// The last step taken.
let completed: Sale.Step
// MARK: Computed
var body: some HTML {
ol(
.class("handshake"),
.custom(name: "aria-label", value: "Handshake: \(completed.label) done")
) {
for step in Sale.Step.allCases {
li(.class(step <= completed ? "handshake__step handshake__step--done" : "handshake__step")) {
step.label
}
}
}
}
}
// MARK: - Extensions
extension Sale.Step {
// MARK: Computed
/// The step's name.
var label: String {
switch self {
case .agreed: "Agreed"
case .paid: "Paid"
case .sent: "Sent"
case .received: "Received"
case .rated: "Rated"
}
}
}
extension Sale {
// MARK: Computed
/// The record's number as shown, e.g. `CCN-48213`.
var reference: String {
"CCN-\(number)"
}
/// Where the sale stands, in a few words, and whether that is done.
var status: (label: String, tone: Chip.Tone) {
switch next {
case .paid: ("Awaiting payment", .warn)
case .sent: ("Awaiting dispatch", .warn)
case .received: ("On its way", .warn)
case .rated: ("Awaiting ratings", .neutral)
default: ("Complete", .good)
}
}
}
@@ -62,6 +62,8 @@ extension Icon {
case document
/// Filter lines.
case filter
/// A circled "i".
case info
/// A key.
case key
/// A padlock.
@@ -90,6 +92,7 @@ extension Icon.Glyph {
case .close: #"<path d="M6 6l12 12M18 6 6 18"></path>"#
case .document: #"<rect x="5" y="3" width="14" height="18" rx="1"></rect><path d="M9 8h6M9 12h6M9 16h3"></path>"#
case .filter: #"<path d="M4 6h16M7 12h10M10 18h4"></path>"#
case .info: #"<circle cx="12" cy="12" r="9"></circle><path d="M12 8v5M12 16h.01"></path>"#
case .key: #"<circle cx="7.5" cy="15.5" r="4.5"></circle><path d="M10.7 12.3 21 2"></path><path d="m18 5 3 3"></path><path d="m15 8 3 3"></path>"#
case .lock: #"<rect x="5" y="11" width="14" height="10" rx="1"></rect><path d="M8 11V7a4 4 0 0 1 8 0v4"></path>"#
case .message: #"<path d="M4 5h16v11H8l-4 4z"></path>"#
@@ -203,11 +203,13 @@ private extension SiteHeader {
unit: "unread",
path: .Route.messages
)
a(
.class("site-header__text-link site-header__text-link--strong from-desktop"),
.href(.Route.sellerDashboard)
) {
"My shop"
if viewer.hasShop {
a(
.class("site-header__text-link site-header__text-link--strong from-tablet"),
.href(.Route.sellerDashboard)
) {
"My shop"
}
}
a(
.class("site-header__account"),