From 45a28094faef5c2e8b4f61f70da7c72a3f99da11 Mon Sep 17 00:00:00 2001 From: Sanchez O'Leary Date: Thu, 25 Jun 2026 17:41:54 -0700 Subject: [PATCH 1/3] Surface merchant application submit failures --- backend/handlers/app_location.go | 2 +- .../merchant/merchant-approval-form.tsx | 17 ++++++++++++----- frontend/context/LocationProvider.tsx | 8 +++++--- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/backend/handlers/app_location.go b/backend/handlers/app_location.go index fa4348d..8944453 100644 --- a/backend/handlers/app_location.go +++ b/backend/handlers/app_location.go @@ -145,8 +145,8 @@ func (a *AppService) AddLocation(w http.ResponseWriter, r *http.Request) { location.OwnerID = *userDid err = a.db.AddLocation(r.Context(), location) if err != nil { - w.WriteHeader(http.StatusBadRequest) a.logger.Logf("invalid location body: %s", err.Error()) + http.Error(w, "Unable to submit merchant application.", http.StatusBadRequest) return } diff --git a/frontend/components/merchant/merchant-approval-form.tsx b/frontend/components/merchant/merchant-approval-form.tsx index be1ab2e..b2a7d2a 100644 --- a/frontend/components/merchant/merchant-approval-form.tsx +++ b/frontend/components/merchant/merchant-approval-form.tsx @@ -190,15 +190,22 @@ export function MerchantApprovalForm() { service_stations: Number(serviceStations), tablet_model: tabletModel === "Other" ? tabletModelOther : tabletModel, messaging_service: messagingService === "Other" ? messagingServiceOther : messagingService, + payment_wallets: [], reference: reference, } setIsSubmitting(true) - await addLocation(newLocation) - setSearchKey(prev => prev + 1) - setIsSubmitting(false); - resetForm() - router.replace("/map") + setError(null) + try { + await addLocation(newLocation) + setSearchKey(prev => prev + 1) + resetForm() + router.replace("/map") + } catch (error) { + setError(error instanceof Error ? error.message : "Unable to submit merchant application.") + } finally { + setIsSubmitting(false) + } } return ( diff --git a/frontend/context/LocationProvider.tsx b/frontend/context/LocationProvider.tsx index e3aa193..4d6cba7 100644 --- a/frontend/context/LocationProvider.tsx +++ b/frontend/context/LocationProvider.tsx @@ -132,14 +132,16 @@ export default function LocationProvider({ children }: { children: ReactNode }) body: JSON.stringify(location) }) if(res.status != 201) { - throw new Error("error adding new location, from controller") + const message = (await res.text()).trim() + throw new Error(message || "Unable to submit merchant application.") } setUserLocationsRef.current((currentLocations) => [...currentLocations, location]) setMapLocationsStatus("available") } - catch { + catch (error) { setMapLocationsStatus("unavailable") - console.error("error adding new location") + console.error("error adding new location", error) + throw error } }, []) From 9de8ccf8cac98dbadba66e458f3f766af6698d73 Mon Sep 17 00:00:00 2001 From: Sanchez O'Leary Date: Thu, 25 Jun 2026 17:56:22 -0700 Subject: [PATCH 2/3] Avoid fragile location upsert conflict target --- backend/db/app_location.go | 180 ++++++++++++++++++++----------------- 1 file changed, 99 insertions(+), 81 deletions(-) diff --git a/backend/db/app_location.go b/backend/db/app_location.go index 97f39fe..052b768 100644 --- a/backend/db/app_location.go +++ b/backend/db/app_location.go @@ -540,86 +540,7 @@ func (s *AppDB) GetAuthedLocations(ctx context.Context, r *structs.LocationsPage } func (a *AppDB) AddLocation(ctx context.Context, location *structs.Location) error { - _, err := a.db.Exec(ctx, ` - INSERT INTO locations ( - google_id, - owner_id, - name, - description, - type, - approval, - approved_at, - street, - city, - state, - zip, - lat, - lng, - phone, - email, - admin_phone, - admin_email, - website, - image_url, - rating, - maps_page, - contact_firstname, - contact_lastname, - contact_phone, - pos_system, - sole_proprietorship, - tipping_policy, - tipping_division, - table_coverage, - service_stations, - tablet_model, - messaging_service, - reference - ) VALUES ( - $1, $2, $3, $4, $5, $6, - CASE WHEN $6 IS TRUE THEN NOW() ELSE NULL END, - $7, $8, $9, $10, - $11, $12, $13, $14, $15, $16, $17, $18, - $19, $20, $21, $22, $23, $24, $25, $26, - $27, $28, $29, $30, $31, $32 - ) - ON CONFLICT (google_id) WHERE active = TRUE - DO UPDATE SET - owner_id = EXCLUDED.owner_id, - name = EXCLUDED.name, - description = EXCLUDED.description, - type = EXCLUDED.type, - approval = EXCLUDED.approval, - approved_at = EXCLUDED.approved_at, - street = EXCLUDED.street, - city = EXCLUDED.city, - state = EXCLUDED.state, - zip = EXCLUDED.zip, - lat = EXCLUDED.lat, - lng = EXCLUDED.lng, - phone = EXCLUDED.phone, - email = EXCLUDED.email, - admin_phone = EXCLUDED.admin_phone, - admin_email = EXCLUDED.admin_email, - website = EXCLUDED.website, - image_url = EXCLUDED.image_url, - rating = EXCLUDED.rating, - maps_page = EXCLUDED.maps_page, - contact_firstname = EXCLUDED.contact_firstname, - contact_lastname = EXCLUDED.contact_lastname, - contact_phone = EXCLUDED.contact_phone, - pos_system = EXCLUDED.pos_system, - sole_proprietorship = EXCLUDED.sole_proprietorship, - tipping_policy = EXCLUDED.tipping_policy, - tipping_division = EXCLUDED.tipping_division, - table_coverage = EXCLUDED.table_coverage, - service_stations = EXCLUDED.service_stations, - tablet_model = EXCLUDED.tablet_model, - messaging_service = EXCLUDED.messaging_service, - reference = EXCLUDED.reference, - active = TRUE, - delete_date = NULL, - delete_reason = NULL;`, + args := []any{ &location.GoogleID, &location.OwnerID, &location.Name, @@ -652,7 +573,104 @@ func (a *AppDB) AddLocation(ctx context.Context, location *structs.Location) err &location.TabletModel, &location.MessagingService, &location.Reference, - ) + } + + result, err := a.db.Exec(ctx, ` + UPDATE locations + SET + owner_id = $2, + name = $3, + description = $4, + type = $5, + approval = $6, + approved_at = CASE WHEN $6 IS TRUE THEN NOW() ELSE NULL END, + street = $7, + city = $8, + state = $9, + zip = $10, + lat = $11, + lng = $12, + phone = $13, + email = $14, + admin_phone = $15, + admin_email = $16, + website = $17, + image_url = $18, + rating = $19, + maps_page = $20, + contact_firstname = $21, + contact_lastname = $22, + contact_phone = $23, + pos_system = $24, + sole_proprietorship = $25, + tipping_policy = $26, + tipping_division = $27, + table_coverage = $28, + service_stations = $29, + tablet_model = $30, + messaging_service = $31, + reference = $32, + active = TRUE, + delete_date = NULL, + delete_reason = NULL + WHERE id = ( + SELECT id + FROM locations + WHERE google_id = $1 + ORDER BY CASE WHEN active = TRUE THEN 0 ELSE 1 END, id DESC + LIMIT 1 + ); + `, args...) + if err != nil { + return fmt.Errorf("error updating existing location: %s", err) + } + + if result.RowsAffected() == 0 { + _, err = a.db.Exec(ctx, ` + INSERT INTO locations ( + google_id, + owner_id, + name, + description, + type, + approval, + approved_at, + street, + city, + state, + zip, + lat, + lng, + phone, + email, + admin_phone, + admin_email, + website, + image_url, + rating, + maps_page, + contact_firstname, + contact_lastname, + contact_phone, + pos_system, + sole_proprietorship, + tipping_policy, + tipping_division, + table_coverage, + service_stations, + tablet_model, + messaging_service, + reference + ) VALUES ( + $1, $2, $3, $4, $5, $6, + CASE WHEN $6 IS TRUE THEN NOW() ELSE NULL END, + $7, $8, $9, $10, + $11, $12, $13, $14, $15, $16, $17, $18, + $19, $20, $21, $22, $23, $24, $25, $26, + $27, $28, $29, $30, $31, $32 + ); + `, args...) + } if err != nil { return fmt.Errorf("error adding location to locations table: %s", err) From 49e7d94820688bc1d44e0b9043bfa315f8c8b034 Mon Sep 17 00:00:00 2001 From: Sanchez O'Leary Date: Thu, 25 Jun 2026 18:08:49 -0700 Subject: [PATCH 3/3] Match location upsert conflict index --- backend/db/app_location.go | 180 +++++++++++++++++-------------------- 1 file changed, 81 insertions(+), 99 deletions(-) diff --git a/backend/db/app_location.go b/backend/db/app_location.go index 052b768..3e6d1cb 100644 --- a/backend/db/app_location.go +++ b/backend/db/app_location.go @@ -540,7 +540,86 @@ func (s *AppDB) GetAuthedLocations(ctx context.Context, r *structs.LocationsPage } func (a *AppDB) AddLocation(ctx context.Context, location *structs.Location) error { - args := []any{ + _, err := a.db.Exec(ctx, ` + INSERT INTO locations ( + google_id, + owner_id, + name, + description, + type, + approval, + approved_at, + street, + city, + state, + zip, + lat, + lng, + phone, + email, + admin_phone, + admin_email, + website, + image_url, + rating, + maps_page, + contact_firstname, + contact_lastname, + contact_phone, + pos_system, + sole_proprietorship, + tipping_policy, + tipping_division, + table_coverage, + service_stations, + tablet_model, + messaging_service, + reference + ) VALUES ( + $1, $2, $3, $4, $5, $6, + CASE WHEN $6 IS TRUE THEN NOW() ELSE NULL END, + $7, $8, $9, $10, + $11, $12, $13, $14, $15, $16, $17, $18, + $19, $20, $21, $22, $23, $24, $25, $26, + $27, $28, $29, $30, $31, $32 + ) + ON CONFLICT (google_id) WHERE active = TRUE AND google_id IS NOT NULL + DO UPDATE SET + owner_id = EXCLUDED.owner_id, + name = EXCLUDED.name, + description = EXCLUDED.description, + type = EXCLUDED.type, + approval = EXCLUDED.approval, + approved_at = EXCLUDED.approved_at, + street = EXCLUDED.street, + city = EXCLUDED.city, + state = EXCLUDED.state, + zip = EXCLUDED.zip, + lat = EXCLUDED.lat, + lng = EXCLUDED.lng, + phone = EXCLUDED.phone, + email = EXCLUDED.email, + admin_phone = EXCLUDED.admin_phone, + admin_email = EXCLUDED.admin_email, + website = EXCLUDED.website, + image_url = EXCLUDED.image_url, + rating = EXCLUDED.rating, + maps_page = EXCLUDED.maps_page, + contact_firstname = EXCLUDED.contact_firstname, + contact_lastname = EXCLUDED.contact_lastname, + contact_phone = EXCLUDED.contact_phone, + pos_system = EXCLUDED.pos_system, + sole_proprietorship = EXCLUDED.sole_proprietorship, + tipping_policy = EXCLUDED.tipping_policy, + tipping_division = EXCLUDED.tipping_division, + table_coverage = EXCLUDED.table_coverage, + service_stations = EXCLUDED.service_stations, + tablet_model = EXCLUDED.tablet_model, + messaging_service = EXCLUDED.messaging_service, + reference = EXCLUDED.reference, + active = TRUE, + delete_date = NULL, + delete_reason = NULL;`, &location.GoogleID, &location.OwnerID, &location.Name, @@ -573,104 +652,7 @@ func (a *AppDB) AddLocation(ctx context.Context, location *structs.Location) err &location.TabletModel, &location.MessagingService, &location.Reference, - } - - result, err := a.db.Exec(ctx, ` - UPDATE locations - SET - owner_id = $2, - name = $3, - description = $4, - type = $5, - approval = $6, - approved_at = CASE WHEN $6 IS TRUE THEN NOW() ELSE NULL END, - street = $7, - city = $8, - state = $9, - zip = $10, - lat = $11, - lng = $12, - phone = $13, - email = $14, - admin_phone = $15, - admin_email = $16, - website = $17, - image_url = $18, - rating = $19, - maps_page = $20, - contact_firstname = $21, - contact_lastname = $22, - contact_phone = $23, - pos_system = $24, - sole_proprietorship = $25, - tipping_policy = $26, - tipping_division = $27, - table_coverage = $28, - service_stations = $29, - tablet_model = $30, - messaging_service = $31, - reference = $32, - active = TRUE, - delete_date = NULL, - delete_reason = NULL - WHERE id = ( - SELECT id - FROM locations - WHERE google_id = $1 - ORDER BY CASE WHEN active = TRUE THEN 0 ELSE 1 END, id DESC - LIMIT 1 - ); - `, args...) - if err != nil { - return fmt.Errorf("error updating existing location: %s", err) - } - - if result.RowsAffected() == 0 { - _, err = a.db.Exec(ctx, ` - INSERT INTO locations ( - google_id, - owner_id, - name, - description, - type, - approval, - approved_at, - street, - city, - state, - zip, - lat, - lng, - phone, - email, - admin_phone, - admin_email, - website, - image_url, - rating, - maps_page, - contact_firstname, - contact_lastname, - contact_phone, - pos_system, - sole_proprietorship, - tipping_policy, - tipping_division, - table_coverage, - service_stations, - tablet_model, - messaging_service, - reference - ) VALUES ( - $1, $2, $3, $4, $5, $6, - CASE WHEN $6 IS TRUE THEN NOW() ELSE NULL END, - $7, $8, $9, $10, - $11, $12, $13, $14, $15, $16, $17, $18, - $19, $20, $21, $22, $23, $24, $25, $26, - $27, $28, $29, $30, $31, $32 - ); - `, args...) - } + ) if err != nil { return fmt.Errorf("error adding location to locations table: %s", err)