diff --git a/web/css/03-settings.css b/web/css/03-settings.css index 030cfbb..ea057df 100644 --- a/web/css/03-settings.css +++ b/web/css/03-settings.css @@ -1,14 +1,43 @@ /* --- settings ----------------------------------------------------------- */ -.popout__link { +/* The EDIT INBOX row: a 50px tap target with the card elevation treatment. */ +.settings-row { display: flex; align-items: center; - justify-content: space-between; gap: 10px; + height: 50px; + padding: 0 14px; + background: var(--surface); + border: 2px solid var(--ink); + box-shadow: var(--shadow-sm); + margin-bottom: 16px; font: 700 12px/1 var(--mono); letter-spacing: 0.5px; + text-transform: uppercase; color: var(--ink); text-decoration: none; - padding: 4px 0; + transition: + transform 0.04s ease, + box-shadow 0.04s ease; +} +.settings-row:active { + transform: translate(2px, 2px); + box-shadow: var(--shadow-xs); +} +.settings-row:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; +} +.settings-row__spacer { + flex: 1; +} +.settings-row__chevron { + color: var(--ink-muted); + font-size: 18px; + line-height: 1; +} +/* Pins the import buttons' width so the busy label swap doesn't reflow the row. */ +.btn--import { + min-width: calc(18ch + 32px); } .settings-account { display: flex; diff --git a/web/src/at_record_web.gleam b/web/src/at_record_web.gleam index 8642bc0..d75538d 100644 --- a/web/src/at_record_web.gleam +++ b/web/src/at_record_web.gleam @@ -58,6 +58,7 @@ fn init(_flags) -> #(Model, Effect(Msg)) { browse_query: "", browse_genre: "", inbox: model.InboxLoading, + confirm_logout: False, ) // modem.init wires up URL routing; clear_query drops a lingering callback ?error=. let routing = modem.init(on_url_change) diff --git a/web/src/at_record_web/model.gleam b/web/src/at_record_web/model.gleam index 7b4eded..cc282f0 100644 --- a/web/src/at_record_web/model.gleam +++ b/web/src/at_record_web/model.gleam @@ -406,6 +406,8 @@ pub type Model { browse_query: String, browse_genre: String, inbox: Inbox, + // Armed like `confirm_remove`: first tap arms, second tap executes. + confirm_logout: Bool, ) } diff --git a/web/src/at_record_web/msg.gleam b/web/src/at_record_web/msg.gleam index 2760bc4..f8039bc 100644 --- a/web/src/at_record_web/msg.gleam +++ b/web/src/at_record_web/msg.gleam @@ -49,6 +49,7 @@ pub type Msg { GotHandleSuggestions(Result(List(HandleSuggestion), rsvp.Error(String))) UseHandleSuggestion(HandleSuggestion) StartLogin + ArmLogout Logout GotLogout SetView(String) diff --git a/web/src/at_record_web/pages/settings.gleam b/web/src/at_record_web/pages/settings.gleam index aa33cee..5e4184d 100644 --- a/web/src/at_record_web/pages/settings.gleam +++ b/web/src/at_record_web/pages/settings.gleam @@ -4,7 +4,7 @@ import at_record_web/model.{type Model, EditInbox, LoggedIn} import at_record_web/msg.{ - type Msg, DiscogsConnect, DiscogsDisconnect, DiscogsImport, + type Msg, ArmLogout, DiscogsConnect, DiscogsDisconnect, DiscogsImport, DiscogsImportWantlist, Logout, } import at_record_web/route @@ -12,6 +12,7 @@ import at_record_web/ui/app_bar as bar import at_record_web/ui/controls as ctl import at_record_web/ui/covers as cov import at_record_web/ui/forms as frm +import gleam/int import gleam/option import lustre/attribute as attr import lustre/element.{type Element, text} @@ -23,10 +24,7 @@ pub fn view(model: Model) -> Element(Msg) { account_header(model), discogs_account_view(model), inbox_row(model), - ctl.button("LOG OUT", ctl.Danger, [ - attr.class("btn--block"), - event.on_click(Logout), - ]), + logout_button(model.confirm_logout), ]) } @@ -52,16 +50,38 @@ fn current_handle(model: Model) -> String { } } +// One link (label, badge, spacer, chevron) so the whole row is a single tap target. fn inbox_row(model: Model) -> Element(Msg) { - cov.card([ - bar.popout_link( - "EDIT INBOX", - route.to_path(EditInbox), - model.inbox_pending_count(model.inbox), + let count = model.inbox_pending_count(model.inbox) + html.a([attr.class("settings-row"), attr.href(route.to_path(EditInbox))], [ + html.span([attr.class("settings-row__label")], [text("EDIT INBOX")]), + case count > 0 { + True -> ctl.badge(int.to_string(count), ctl.Accent) + False -> element.none() + }, + html.span([attr.class("settings-row__spacer")], []), + html.span( + [ + attr.class("settings-row__chevron"), + attr.attribute("aria-hidden", "true"), + ], + [text("›")], ), ]) } +// Same armed pattern as the record-detail REMOVE action (see ArmRemove). +fn logout_button(confirm_logout: Bool) -> Element(Msg) { + let #(label, on_click) = case confirm_logout { + True -> #("REALLY LOG OUT?", Logout) + False -> #("LOG OUT", ArmLogout) + } + ctl.button(label, ctl.Danger, [ + attr.class("btn--block"), + event.on_click(on_click), + ]) +} + fn discogs_account_view(model: Model) -> Element(Msg) { cov.card([ frm.section_label("DISCOGS ACCOUNT"), @@ -87,6 +107,7 @@ fn discogs_account_view(model: Model) -> Element(Msg) { }, ctl.Dark, [ + attr.class("btn--import"), attr.disabled(model.discogs.importing), event.on_click(DiscogsImport), ], @@ -98,6 +119,7 @@ fn discogs_account_view(model: Model) -> Element(Msg) { }, ctl.Ghost, [ + attr.class("btn--import"), attr.disabled(model.discogs.importing), event.on_click(DiscogsImportWantlist), ], diff --git a/web/src/at_record_web/update.gleam b/web/src/at_record_web/update.gleam index 273af15..2f2b611 100644 --- a/web/src/at_record_web/update.gleam +++ b/web/src/at_record_web/update.gleam @@ -14,9 +14,9 @@ import at_record_web/model.{ import at_record_web/money import at_record_web/msg.{ type Msg, type ScanLookup, AddScannedItem, AddSuggestion, AmendField, - AppliedProposal, ApplyProposal, ArmRemove, ArtistSearch, BarcodeDetected, - BrowseAdd, BrowseGenre, BrowseQuery, CameraUnsupported, ClearNotice, - CoverFileChosen, CoverUploaded, DiscogsConnect, DiscogsDisconnect, + AppliedProposal, ApplyProposal, ArmLogout, ArmRemove, ArtistSearch, + BarcodeDetected, BrowseAdd, BrowseGenre, BrowseQuery, CameraUnsupported, + ClearNotice, CoverFileChosen, CoverUploaded, DiscogsConnect, DiscogsDisconnect, DiscogsImport, DiscogsImportWantlist, DiscogsLoadMore, DiscogsSearch, DiscogsVinylOnly, EntryAction, FormArtist, FormCounterparty, FormFolder, FormFormat, FormPriceAmount, FormPriceCurrency, FormRating, FormSleeveGrade, @@ -50,6 +50,7 @@ pub fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { selected: Some(entry_id), editing: False, confirm_remove: False, + confirm_logout: False, timeline: [], release_info: None, busy: True, @@ -66,6 +67,7 @@ pub fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { selected: None, editing: False, confirm_remove: False, + confirm_logout: False, timeline: [], release_info: None, browse_adding: None, @@ -135,6 +137,8 @@ pub fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { ) } + ArmLogout -> #(Model(..model, confirm_logout: True), effect.none()) + Logout -> #( Model( ..model, @@ -142,6 +146,7 @@ pub fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { shelf: ShelfLoading, notice: None, avatar: None, + confirm_logout: False, ), logout(), ) diff --git a/web/test/support.gleam b/web/test/support.gleam index ba3d5e6..52ac0c2 100644 --- a/web/test/support.gleam +++ b/web/test/support.gleam @@ -55,6 +55,7 @@ pub fn base() -> Model { browse_query: "", browse_genre: "", inbox: model.InboxLoading, + confirm_logout: False, ) }