[public-store-distance] Consume the public distance contract, stop labelling it as unavailable

Guest browsing showed 'Distância indisponível' on every store card and a
'--' tile on store detail, because the public locator response carries no
distance and StoreSummary.init(publicItem:) hardcoded nil.

Backend contract (docs/plans/public-store-distance-consumer.md): distance
is a number, never null, 0 means unavailable. Implemented app-side ahead of
the backend - every change is forward-compatible, and the part that removes
the broken label works with no backend at all.

- PublicStoreListItem / PublicStoreDetail: + distance (optional, so today's
  responses without the field still decode).
- StoreSummary.init(publicItem:): normalize the 0 sentinel to nil, so one
  representation of 'unknown' reaches the label and the max-distance filter.
- fetchStoreDetail: send state/city from GuestLocationStore by default -
  the server needs them to resolve the city centroid.
- formatDistance: empty string for nil/0/negative. It previously returned
  'Distância indisponível' for nil, and - found by the new test - '0 m' for
  0 and '-1000 m' for a negative.
- StoreCard: drop the distance segment and its '·' separator together,
  otherwise the row ended in a dangling separator.
- StoreDetailView: drop the DISTÂNCIA tile and its divider instead of '--'.

Also fixes the same label in the authenticated flow when the user declined
location and has no address coordinates.

Tests: PublicStoreDistanceTests (decode with/without the field, passthrough,
0 normalization) + the empty case in HomeViewFilteringTests.
This commit is contained in:
Daniel Arantes Loverde
2026-08-27 16:33:01 -03:00
parent 0f0672f18d
commit a0e4375914
10 changed files with 154 additions and 33 deletions

View File

@@ -34,13 +34,31 @@ final class PublicLocationService: @unchecked Sendable {
}
/// No guest token this route is fully public/unauthenticated per the doc.
func fetchStoreDetail(identifier: String) async throws -> PublicStoreDetail {
///
/// `state`/`city` are optional on the server and are what let it resolve
/// the city centroid the store `distance` is measured from; without them
/// the response carries `distance: 0` (see
/// docs/plans/public-store-distance-consumer.md). Defaults to whatever the
/// visitor picked in "ENTREGAR EM:".
func fetchStoreDetail(
identifier: String,
state: String? = GuestLocationStore.shared.selectedState,
city: String? = GuestLocationStore.shared.selectedCity
) async throws -> PublicStoreDetail {
let encodedIdentifier = identifier.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) ?? identifier
var query: [URLQueryItem] = []
if let state, state.isEmpty == false {
query.append(URLQueryItem(name: "state", value: state))
}
if let city, city.isEmpty == false {
query.append(URLQueryItem(name: "city", value: city))
}
let req = ApiRequest(
path: "/api/public/store/\(encodedIdentifier)",
method: "GET",
module: .none,
requiresAuth: false,
queryItems: query,
baseURLOverride: ApiConfig.pediFoodsBFFURL
)
let envelope: ApiEnvelope<PublicStoreDetail> = try await client.send(req)