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

This commit is contained in:
javier committed 2026-09-21 11:16:52 +02:00
1 parent 6d7ab4bcba
commit ea89ea892a
37 files changed
+4173 -43

No files matched your search

@@ -0,0 +1,14 @@
import Foundation
extension Int {
// MARK: Computed
/// The amount of euro cents as a price: whole euros bare (`€240`), anything else to the cent (`€12.50`).
var euros: String {
isMultiple(of: 100)
? "€\(self / 100)"
: String(format: "€%d.%02d", self / 100, self % 100)
}
}
@@ -0,0 +1,89 @@
import Foundation
import Persistence
extension Listing {
// MARK: Computed
/// The chip stating the copy's grade or condition.
///
/// A slab reads `CGC 9.8` — good from 9.0 up, a warning below 7.0 — a raw issue `Raw · NM`, and a collected edition its condition.
var chip: (label: String, tone: Chip.Tone, isCertified: Bool) {
guard let grader, let grade else {
return (
kind == .singleIssues ? "Raw · \(condition)" : condition,
.neutral,
false
)
}
return (
"\(grader) \(grade.gradeLabel)",
grade >= 9 ? .good : grade < 7 ? .warn : .neutral,
true
)
}
/// The format the listing is published in, or `nil` when its token names none.
var kind: Format? {
.init(rawValue: format)
}
/// The path of the listing's detail screen.
var path: String {
.Route.listing(slug)
}
/// The line under a card's title: the publisher, year, and printing, led by the format for a collected edition.
var summary: String {
let lead = kind == .singleIssues ? nil : kind?.singular
return [lead, publisher, String(year), printing]
.compactMap(\.self)
.joined(separator: " · ")
}
}
extension Shop {
// MARK: Computed
/// The shop's location, e.g. `Amsterdam, NL`.
var location: String {
"\(city), \(country)"
}
/// The uppercased initials of the first two words of the shop's name that start with a letter.
var monogram: String {
name
.split(separator: " ")
.filter { $0.first?.isLetter == true }
.prefix(2)
.compactMap(\.first)
.map { String($0).uppercased() }
.joined()
}
/// The path of the shop's public screen.
var path: String {
.Route.shop(slug)
}
/// The shop's standing in a line: where it is, since when, and how it is rated.
var standing: String {
"\(location) · selling on CCN since \(sinceYear) · \(String(format: "%.1f", rating)) from \(ratedSales) rated sales"
}
}
extension Double {
// MARK: Computed
/// The grade to one decimal place, e.g. `9.8`.
var gradeLabel: String {
String(format: "%.1f", self)
}
}
@@ -1,3 +1,5 @@
import Foundation
extension String {
/// A namespace for the paths of the site's screens.
///
@@ -35,5 +37,36 @@ extension String {
static let terms = "/terms"
/// The want list.
static let wants = "/wants"
/// The path of a listing's detail screen.
/// - Parameter slug: the listing's URL slug.
static func listing(
_ slug: String
) -> String {
"/listing/\(slug)"
}
/// The path of a shop's public screen.
/// - Parameter slug: the shop's URL slug.
static func shop(
_ slug: String
) -> String {
"/shop/\(slug)"
}
/// The path of the sign-in screen, returning to the given path afterwards.
/// - Parameter path: the path the visitor's gated action started from.
static func signIn(
returningTo path: String
) -> String {
"\(signIn)?return=\(path.addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? "")"
}
}
}
// MARK: - Constants
private extension CharacterSet {
/// The characters a query value carries unescaped.
static let urlQueryValueAllowed = CharacterSet.urlQueryAllowed.subtracting(.init(charactersIn: "&=+?#"))
}