Skip to content

Commit 45d0881

Browse files
dnywhcursoragent
andauthored
fix(listings): improve unexpected error reporting (#117)
* fix listing error reporting Co-authored-by: Cursor <cursoragent@cursor.com> * improve listing error traceability Co-authored-by: Cursor <cursoragent@cursor.com> * format * match listing support link colour Co-authored-by: Cursor <cursoragent@cursor.com> * fix listing support email links Co-authored-by: Cursor <cursoragent@cursor.com> * add support link hover state Co-authored-by: Cursor <cursoragent@cursor.com> * harden listing support diagnostics Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 2ae16a1 commit 45d0881

15 files changed

Lines changed: 212 additions & 19 deletions

File tree

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ For local-first Supabase development:
8686

8787
The repo defaults `NEXT_PUBLIC_SUPABASE_URL` to the local stack so development does not need the hosted Peels project. Peels uses ports `54331``54334` so it can run beside other local Supabase projects. If `.env.local` still points at hosted Supabase, update it; copying `.env.example` later may not overwrite an existing file.
8888

89+
Keep the local URL, anon key, and service-role key together. Never reuse the
90+
hosted secret key for local development.
91+
8992
If you need to serve [Supabase edge functions](https://github.com/peels-org/peels/blob/main/supabase/functions) locally, copy `supabase/functions/.env.example` to `supabase/functions/.env`. That file is loaded by `supabase start`. Use `supabase functions serve` only when you want hot reload while editing function code. Production secrets should remain dashboard-managed for now.
9093

9194
For the fuller operational walkthrough, including GitHub/Vercel dashboard setup and fresh-computer bootstrap, see [docs/supabase-local-first.md](./docs/supabase-local-first.md).

docs/supabase-data-architecture.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -256,8 +256,9 @@ Then run the Storage API cleanup script in dry-run mode:
256256
npm run media:cleanup-orphans
257257
```
258258

259-
For hosted environments, pass the project URL and service-role key explicitly
260-
and include `--allow-remote`. Deletion requires both `--delete-orphans` and
259+
For hosted environments, pass the project URL and server-only key explicitly
260+
and include `--allow-remote`. The newer `sb_secret_...` key is preferred; keep
261+
the existing `SUPABASE_SERVICE_ROLE_KEY` variable name. Deletion requires both `--delete-orphans` and
261262
`--confirm-delete-orphans`, plus an explicit `--before` cutoff; this keeps the
262263
default path inspect-only and protects in-flight uploads whose database
263264
references have not been written yet.

docs/supabase-local-first.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,11 @@ Then make sure `.env.local` has:
8888
```bash
8989
NEXT_PUBLIC_SUPABASE_URL=http://127.0.0.1:54331
9090
NEXT_PUBLIC_SUPABASE_ANON_KEY=<paste the ANON_KEY value here>
91+
SUPABASE_SERVICE_ROLE_KEY=<paste the SERVICE_ROLE_KEY value here>
9192
```
9293

93-
Do not mix the hosted project URL with the local anon key. That combination fails with `Invalid API key`.
94+
Use all three values printed by `npm run supabase:env`. Do not mix local and
95+
hosted URLs or keys.
9496

9597
Finally:
9698

@@ -108,6 +110,7 @@ Use this shape in `.env.local` before `npm run test:e2e:prod`:
108110
NEXT_PUBLIC_SITE_URL=http://127.0.0.1:3000
109111
NEXT_PUBLIC_SUPABASE_URL=http://127.0.0.1:54331
110112
NEXT_PUBLIC_SUPABASE_ANON_KEY=<paste the ANON_KEY value from npm run supabase:env>
113+
SUPABASE_SERVICE_ROLE_KEY=<paste the SERVICE_ROLE_KEY value from npm run supabase:env>
111114
```
112115

113116
Recommended sequence:
@@ -116,7 +119,7 @@ Recommended sequence:
116119
2. Run `npm run supabase:reset`
117120
3. Run `npm run seed:local-media`
118121
4. Run `npm run supabase:env`
119-
5. Paste the printed `ANON_KEY` into `.env.local`
122+
5. Paste the printed `ANON_KEY` and `SERVICE_ROLE_KEY` into `.env.local`
120123
6. Run `npm run test:e2e` or `npm run test:e2e:prod`
121124

122125
If `.env.local` still points at `https://mfnaqdyunuafbwukbbyr.supabase.co`, the smoke suite will not see the seeded local listings, demo accounts, or demo chat thread.

e2e/listings.spec.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,55 @@ test("new listing form shows validation feedback when location is missing", asyn
114114
).toBeVisible();
115115
});
116116

117+
test("unexpected listing errors offer a traceable support email", async ({
118+
page,
119+
}) => {
120+
await page.setExtraHTTPHeaders({
121+
"x-peels-e2e-listing-error": "unexpected",
122+
});
123+
await signIn(page, {
124+
email: HOST_EMAIL,
125+
redirectTo:
126+
"/profile/listings/new/business?token=test-secret#private-fragment",
127+
});
128+
await mockMapTilerGeocoding(page);
129+
130+
await page.locator("#name").fill("Temporary support email test");
131+
await page
132+
.locator("#description")
133+
.fill("A test-only description for the support email flow.");
134+
await page.locator("#country").selectOption("AU");
135+
const searchInput = page.getByTestId("listing-location-search-input");
136+
await searchInput.fill("Newtown");
137+
await page.getByRole("option", { name: /Newtown/ }).click();
138+
await page.locator('input[name="legal_agreement"]').check();
139+
await page.getByTestId("listing-write-submit").click();
140+
141+
const alert = page.locator("aside[role='alert']");
142+
await expect(alert).toContainText("An unexpected error occurred.");
143+
const emailLink = alert.getByRole("link", { name: "email us" });
144+
await expect(emailLink).toBeVisible();
145+
146+
const href = await emailLink.getAttribute("href");
147+
expect(href).not.toBeNull();
148+
expect(href).not.toContain("+");
149+
expect(href).toContain("%20");
150+
const emailUrl = new URL(href ?? "");
151+
expect(emailUrl.protocol).toBe("mailto:");
152+
expect(emailUrl.searchParams.get("subject")).toBe(
153+
"Problem creating a Peels listing"
154+
);
155+
expect(emailUrl.searchParams.get("body")).toMatch(
156+
/Error reference: listing-[0-9a-f-]+/
157+
);
158+
expect(emailUrl.searchParams.get("body")).toContain("Listing type: business");
159+
expect(emailUrl.searchParams.get("body")).toContain(
160+
"/profile/listings/new/business"
161+
);
162+
expect(emailUrl.searchParams.get("body")).not.toContain("test-secret");
163+
expect(emailUrl.searchParams.get("body")).not.toContain("private-fragment");
164+
});
165+
117166
test("listing location search picks a geocoding result", async ({ page }) => {
118167
await signIn(page, {
119168
email: HOST_EMAIL,

messages/de.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@
118118
"updateFirstNameFailed": "Entschuldigung, wir konnten deinen Vornamen nicht aktualisieren.",
119119
"updateNewsletterFailed": "Entschuldigung, wir konnten deine Newsletter-Einstellung nicht aktualisieren.",
120120
"unexpected": "Ein unerwarteter Fehler ist aufgetreten. Bitte versuche es später erneut.",
121+
"unexpectedContact": "Wenn das weiterhin passiert, <contact>schreib uns eine E-Mail</contact>.",
122+
"unexpectedEmailSubject": "Problem beim Erstellen eines Peels-Eintrags",
123+
"unexpectedEmailBody": "Beim Speichern eines Peels-Eintrags ist ein Fehler aufgetreten.\n\nFehlerreferenz: {reference}\nSeite: {pageUrl}\nEintragstyp: {listingType}\nZeit: {timestamp}\n\nBitte ergänze unten weitere hilfreiche Angaben. Gib keine Passwörter oder andere vertrauliche Informationen an.",
121124
"validationSummary": "Bitte behebe {count, plural, one {den obigen Fehler} other {die obigen Fehler}} und versuche es erneut.",
122125
"verificationChallenge": "Bitte schließe die Verifizierung ab.",
123126
"verificationFailed": "Die Verifizierung ist fehlgeschlagen. Bitte versuche es erneut.",
@@ -459,7 +462,7 @@
459462
"showOnMap": "Diesen Eintrag auf der Karte anzeigen",
460463
"hideFromMap": "Diesen Eintrag von der Karte ausblenden",
461464
"adminHint": "Nur für Admins sichtbare Steuerelemente für diesen Eintrag.",
462-
"stubSettings": "Stub-Einstellungen",
465+
"stubSettings": "Präsentation",
463466
"regularListing": "Dies ist ein regulärer Eintrag, der dir gehört",
464467
"stubListing": "Dieser Eintrag ist ein Stub, den andere beanspruchen können",
465468
"stubActiveHint": "Dieser Eintrag enthält keine deiner Kontaktinformationen. Andere können ihn als eigenen Eintrag beanspruchen und übernehmen.",

messages/en.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@
118118
"updateFirstNameFailed": "Sorry, we couldn’t update your first name.",
119119
"updateNewsletterFailed": "Sorry, we couldn’t update your newsletter preference.",
120120
"unexpected": "An unexpected error occurred. Please try again later.",
121+
"unexpectedContact": "If this keeps happening, <contact>email us</contact>.",
122+
"unexpectedEmailSubject": "Problem creating a Peels listing",
123+
"unexpectedEmailBody": "I encountered an error while saving a Peels listing.\n\nError reference: {reference}\nPage: {pageUrl}\nListing type: {listingType}\nTime: {timestamp}\n\nPlease add any other helpful details below. Do not include passwords or other sensitive information.",
121124
"validationSummary": "Please fix the above error{count, plural, one {} other {s}} and then try again.",
122125
"verificationChallenge": "Please complete the verification challenge.",
123126
"verificationFailed": "Verification failed. Please try again.",
@@ -459,7 +462,7 @@
459462
"showOnMap": "Show this listing on the map",
460463
"hideFromMap": "Hide this listing from the map",
461464
"adminHint": "Admin-only controls for this listing.",
462-
"stubSettings": "Stub settings",
465+
"stubSettings": "Presentation",
463466
"regularListing": "This is a regular listing owned by you",
464467
"stubListing": "This listing is a stub that others can claim",
465468
"stubActiveHint": "This listing will not contain your contact information. Others can claim it as their own and take it over.",

messages/es.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@
118118
"updateFirstNameFailed": "Lo sentimos, no pudimos actualizar tu nombre.",
119119
"updateNewsletterFailed": "Lo sentimos, no pudimos actualizar tu preferencia del boletín.",
120120
"unexpected": "Ocurrió un error inesperado. Inténtalo de nuevo más tarde.",
121+
"unexpectedContact": "Si sigue ocurriendo, <contact>envíanos un correo</contact>.",
122+
"unexpectedEmailSubject": "Problema al crear un anuncio en Peels",
123+
"unexpectedEmailBody": "Se produjo un error al guardar un anuncio en Peels.\n\nReferencia del error: {reference}\nPágina: {pageUrl}\nTipo de anuncio: {listingType}\nHora: {timestamp}\n\nAñade cualquier otro detalle útil a continuación. No incluyas contraseñas ni otra información confidencial.",
121124
"validationSummary": "Corrige {count, plural, one {el error anterior} other {los errores anteriores}} y vuelve a intentarlo.",
122125
"verificationChallenge": "Completa la verificación.",
123126
"verificationFailed": "La verificación falló. Inténtalo de nuevo.",
@@ -459,7 +462,7 @@
459462
"showOnMap": "Mostrar este anuncio en el mapa",
460463
"hideFromMap": "Ocultar este anuncio del mapa",
461464
"adminHint": "Controles solo para administradores de este anuncio.",
462-
"stubSettings": "Configuración de ficha preliminar",
465+
"stubSettings": "Presentación",
463466
"regularListing": "Este es un anuncio normal de tu propiedad",
464467
"stubListing": "Este anuncio es una ficha preliminar que otras personas pueden reclamar",
465468
"stubActiveHint": "Este anuncio no contendrá tu información de contacto. Otras personas pueden reclamarlo como propio y hacerse cargo.",

messages/fr.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@
118118
"updateFirstNameFailed": "Désolé, nous n’avons pas pu mettre à jour votre prénom.",
119119
"updateNewsletterFailed": "Désolé, nous n’avons pas pu mettre à jour votre préférence d’infolettre.",
120120
"unexpected": "Une erreur inattendue s’est produite. Veuillez réessayer plus tard.",
121+
"unexpectedContact": "Si le problème persiste, <contact>envoyez-nous un e-mail</contact>.",
122+
"unexpectedEmailSubject": "Problème lors de la création d’une annonce Peels",
123+
"unexpectedEmailBody": "Une erreur s’est produite lors de l’enregistrement d’une annonce Peels.\n\nRéférence de l’erreur : {reference}\nPage : {pageUrl}\nType d’annonce : {listingType}\nHeure : {timestamp}\n\nAjoutez ci-dessous tout autre détail utile. N’indiquez aucun mot de passe ni aucune autre information sensible.",
121124
"validationSummary": "Veuillez corriger {count, plural, one {l’erreur ci-dessus} other {les erreurs ci-dessus}} puis réessayer.",
122125
"verificationChallenge": "Veuillez terminer la vérification de sécurité.",
123126
"verificationFailed": "La vérification a échoué. Veuillez réessayer.",
@@ -459,7 +462,7 @@
459462
"showOnMap": "Afficher cette annonce sur la carte",
460463
"hideFromMap": "Masquer cette annonce de la carte",
461464
"adminHint": "Contrôles réservés à l’administration pour cette annonce.",
462-
"stubSettings": "Paramètres du brouillon",
465+
"stubSettings": "Présentation",
463466
"regularListing": "Ceci est une annonce normale créée par vous",
464467
"stubListing": "Cette annonce est un brouillon que d’autres personnes peuvent revendiquer",
465468
"stubActiveHint": "Cette annonce ne contiendra pas vos coordonnées. D’autres personnes pourront la revendiquer et la reprendre.",

messages/pt-BR.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@
118118
"updateFirstNameFailed": "Desculpe, não foi possível atualizar o seu nome.",
119119
"updateNewsletterFailed": "Desculpe, não foi possível atualizar a sua preferência do boletim.",
120120
"unexpected": "Ocorreu um erro inesperado. Tente novamente mais tarde.",
121+
"unexpectedContact": "Se isso continuar acontecendo, <contact>envie-nos um e-mail</contact>.",
122+
"unexpectedEmailSubject": "Problema ao criar um anúncio no Peels",
123+
"unexpectedEmailBody": "Ocorreu um erro ao salvar um anúncio no Peels.\n\nReferência do erro: {reference}\nPágina: {pageUrl}\nTipo de anúncio: {listingType}\nHorário: {timestamp}\n\nAdicione abaixo qualquer outro detalhe útil. Não inclua senhas nem outras informações confidenciais.",
121124
"validationSummary": "Corrija {count, plural, one {o erro acima} other {os erros acima}} e tente novamente.",
122125
"verificationChallenge": "Conclua a verificação de segurança.",
123126
"verificationFailed": "A verificação falhou. Tente novamente.",
@@ -459,7 +462,7 @@
459462
"showOnMap": "Mostrar este anúncio no mapa",
460463
"hideFromMap": "Ocultar este anúncio do mapa",
461464
"adminHint": "Controles apenas para admin deste anúncio.",
462-
"stubSettings": "Configurações de rascunho",
465+
"stubSettings": "Apresentação",
463466
"regularListing": "Este é um anúncio normal criado por você",
464467
"stubListing": "Este anúncio é um rascunho que outras pessoas podem reivindicar",
465468
"stubActiveHint": "Este anúncio não mostrará as suas informações de contato. Outras pessoas podem reivindicá-lo e assumir o controle.",

playwright.config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ export default defineConfig({
1313
? {
1414
...sharedWebServer,
1515
command: `npm run dev -- --hostname ${playwrightHost} --port ${playwrightPort}`,
16+
env: {
17+
PEELS_E2E: "1",
18+
},
1619
}
1720
: undefined,
1821
});

0 commit comments

Comments
 (0)