Files
ccn/Services/Website/Sources/Library/Internal/Pages/EventsPage.swift
T

138 lines
4.9 KiB
Swift

import Elementary
import Foundation
import Infrastructure
import Persistence
/// The events where shops trade in person: agree on the site, skip shipping, collect the copy at the con.
struct EventsPage {
// MARK: Properties
let analytics: Analytics?
let assetVersion: String?
let locale: Locale
/// The events still to come, soonest first, each with the number of listings that can be picked up there.
let events: [(event: Event, listingCount: Int)]
/// The moment "this weekend" is judged from.
let now: Date
/// The signed-in viewer, or `nil` when signed out.
let viewer: Viewer?
// MARK: Initializers
/// Creates the events screen.
/// - Parameters:
/// - events: the events still to come, soonest first.
/// - listings: every listing for sale, counted per event by the shops trading there.
/// - viewer: the signed-in viewer, or `nil` (the default) when signed out.
/// - now: the moment "this weekend" is judged from; the present by default.
/// - assetVersion: the version token appended to the page's asset URLs, or `nil` (the default) to leave them unversioned.
/// - analytics: the analytics tracker embedded in the document head, or `nil` (the default) to omit it.
init(
events: [Event],
listings: [Listing],
viewer: Viewer? = nil,
now: Date = .init(),
assetVersion: String? = nil,
analytics: Analytics? = nil
) {
self.analytics = analytics
self.assetVersion = assetVersion
self.events = events.map { event in
let shops = Set(event.shops.map(\.slug))
return (event, listings.count { shops.contains($0.shop.slug) })
}
self.locale = .init(identifier: "en")
self.now = now
self.viewer = viewer
}
}
// MARK: - Page
extension EventsPage: Page {
// MARK: Computed
var content: some HTML {
SiteLayout(viewer: viewer) {
div(.class("page wants")) {
div(.class("events__intro")) {
h1(.class("type-h1")) {
"Pick up at a con"
}
p(.class("type-lead")) {
"Sellers mark which events they'll trade at. Agree on CCN, skip shipping, collect it in person."
}
}
if events.isEmpty {
div(.class("empty")) {
h2(.class("type-panel")) {
"No events coming up"
}
p(.class("type-body-sm")) {
"Every copy still ships, tracked."
}
}
}
ul(.class("wants__list")) {
ForEach(events) { entry in
li {
a(
.class("card events__event"),
.href(.Route.pickup(at: entry.event.slug))
) {
span(.class("wants__top")) {
span(.class("events__name")) {
strong {
entry.event.name
}
span(.class("type-small")) {
Date.span(from: entry.event.startsOn, to: entry.event.endsOn)
}
}
if entry.event.startsOn.isWithinAWeek(of: now) {
Chip("This weekend", tone: .warn)
}
}
span(.class("type-small")) {
entry.event.venue
}
span(.class("events__chips")) {
Chip("\(entry.event.shops.count) \(entry.event.shops.count == 1 ? "shop" : "shops") trading")
Chip("\(entry.listingCount) \(entry.listingCount == 1 ? "listing" : "listings") for pickup")
}
}
}
}
}
p(.class("type-small wants__note")) {
"Selling? Pick the events you'll trade at on your seller dashboard."
}
}
}
}
var scripts: [any Asset] {
[StaticFile.shared]
}
var stylesheets: [any Asset] {
[StaticFile.shared, StaticFile.trade]
}
var summary: String? {
"Comic cons where CCN shops trade in person: agree on a copy online, collect it at the event."
}
var title: String {
"Events — CCN"
}
}