diff --git a/frontend/src/components/gallery/GuestNamePromptModal.tsx b/frontend/src/components/gallery/GuestNamePromptModal.tsx index 76dd135a..dfbc5b53 100644 --- a/frontend/src/components/gallery/GuestNamePromptModal.tsx +++ b/frontend/src/components/gallery/GuestNamePromptModal.tsx @@ -139,16 +139,45 @@ export const GuestNamePromptModal: React.FC = ({ )} - +

+ {t( + 'gallery.guestPrompt.returningHint', + 'Been here before? Your earlier picks are still saved.' + )} +

+ + diff --git a/frontend/src/components/gallery/__tests__/GuestNamePromptModal.recoveryNudge.test.tsx b/frontend/src/components/gallery/__tests__/GuestNamePromptModal.recoveryNudge.test.tsx new file mode 100644 index 00000000..2e4b244b --- /dev/null +++ b/frontend/src/components/gallery/__tests__/GuestNamePromptModal.recoveryNudge.test.tsx @@ -0,0 +1,79 @@ +/** + * The returning-guest nudge on the registration form (#1210). + * + * A guest who fills this form in again becomes a second gallery_guests row and + * their earlier likes and favourites stop counting as theirs. Recovery has + * always been here to prevent exactly that — as a small link under the submit + * button, which people read as fine print and skipped, so duplicates kept + * accumulating even for guests who had given an email the first time. + * + * These pin that it is findable and that it routes into recovery rather than + * registering. What they deliberately do NOT pin is any check against the + * typed email: asking the server whether an address is already registered + * would answer "is this person in this gallery" to anyone who asked. + */ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { describe, expect, it, vi, beforeEach } from 'vitest'; + +import { GuestNamePromptModal } from '../GuestNamePromptModal'; + +const closePrompt = vi.fn(); +const openRecovery = vi.fn(); +const register = vi.fn(); + +vi.mock('react-i18next', async () => { + const actual = await vi.importActual('react-i18next'); + return { + ...actual, + useTranslation: () => ({ + t: (_key: string, fallback?: any) => (typeof fallback === 'string' ? fallback : _key), + i18n: { language: 'en' } + }) + }; +}); + +vi.mock('../../../contexts/GuestIdentityContext', () => ({ + useGuestIdentity: () => ({ + promptOpen: true, + closePrompt, + register, + openRecovery, + }), +})); + +describe('returning-guest nudge (#1210)', () => { + beforeEach(() => { + closePrompt.mockReset(); + openRecovery.mockReset(); + register.mockReset(); + }); + + it('tells the guest their earlier picks still exist', () => { + render(); + // Worded around what they lose by missing it. "I've been here before" + // reads as a greeting; this reads as a reason to stop and click. + expect(screen.getByText(/your earlier picks are still saved/i)).toBeInTheDocument(); + }); + + it('routes into recovery instead of registering a second time', async () => { + render(); + + await userEvent.click(screen.getByRole('button', { name: /get them back/i })); + + expect(openRecovery).toHaveBeenCalledTimes(1); + expect(register).not.toHaveBeenCalled(); + }); + + it('leaves the ordinary registration path alone', async () => { + render(); + + await userEvent.type(screen.getByLabelText(/your name/i), 'Tina'); + await userEvent.click(screen.getByRole('button', { name: /^Continue$/i })); + + // A first-time guest still just registers — the nudge is an offer beside + // the form, not a step in front of it. + expect(register).toHaveBeenCalled(); + expect(openRecovery).not.toHaveBeenCalled(); + }); +}); diff --git a/frontend/src/i18n/locales/de.json b/frontend/src/i18n/locales/de.json index 367982bc..bf4bf9c5 100644 --- a/frontend/src/i18n/locales/de.json +++ b/frontend/src/i18n/locales/de.json @@ -1046,7 +1046,8 @@ "emailLabel": "E-Mail (optional)", "emailPlaceholder": "Sie@beispiel.de", "submit": "Weiter", - "alreadyHere": "Ich war schon einmal hier" + "returningHint": "Schon einmal hier gewesen? Ihre bisherige Auswahl ist noch gespeichert.", + "recoverPicks": "Auswahl zurückholen" }, "footer": { "forgetMeConfirm": "Ihr Name und Ihre Auswahl werden aus dieser Galerie entfernt.", diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json index 2fcf7919..935a8483 100644 --- a/frontend/src/i18n/locales/en.json +++ b/frontend/src/i18n/locales/en.json @@ -583,7 +583,8 @@ "emailLabel": "Email (optional)", "emailPlaceholder": "you@example.com", "submit": "Continue", - "alreadyHere": "I've been here before" + "returningHint": "Been here before? Your earlier picks are still saved.", + "recoverPicks": "Get them back" }, "footer": { "forgetMeConfirm": "Your name and selections will be removed from this gallery.", diff --git a/frontend/src/i18n/locales/fr.json b/frontend/src/i18n/locales/fr.json index 229109ba..4bf4a9f0 100644 --- a/frontend/src/i18n/locales/fr.json +++ b/frontend/src/i18n/locales/fr.json @@ -326,7 +326,8 @@ "emailLabel": "E-mail (optionnel)", "emailPlaceholder": "vous@example.com", "submit": "Continuer", - "alreadyHere": "J'ai déjà été ici" + "returningHint": "Déjà venu ? Vos choix précédents sont toujours enregistrés.", + "recoverPicks": "Les récupérer" }, "footer": { "forgetMeConfirm": "Votre nom et vos sélections seront supprimés de cette galerie.", diff --git a/frontend/src/i18n/locales/nl.json b/frontend/src/i18n/locales/nl.json index 7c0846fd..e47dacab 100644 --- a/frontend/src/i18n/locales/nl.json +++ b/frontend/src/i18n/locales/nl.json @@ -330,7 +330,8 @@ "emailLabel": "E-mail (optioneel)", "emailPlaceholder": "u@voorbeeld.nl", "submit": "Doorgaan", - "alreadyHere": "Ik ben hier al eerder geweest" + "returningHint": "Al eerder hier geweest? Uw eerdere keuzes zijn nog bewaard.", + "recoverPicks": "Keuzes terughalen" }, "footer": { "forgetMeConfirm": "Uw naam en selecties worden verwijderd uit deze galerij.", diff --git a/frontend/src/i18n/locales/pt.json b/frontend/src/i18n/locales/pt.json index 52033cad..0ccba946 100644 --- a/frontend/src/i18n/locales/pt.json +++ b/frontend/src/i18n/locales/pt.json @@ -337,7 +337,8 @@ "emailLabel": "E-mail (opcional)", "emailPlaceholder": "voce@exemplo.pt", "submit": "Continuar", - "alreadyHere": "Já estive aqui antes" + "returningHint": "Já esteve aqui? As suas escolhas anteriores continuam guardadas.", + "recoverPicks": "Recuperá-las" }, "footer": { "forgetMeConfirm": "O seu nome e seleções serão removidos desta galeria.", diff --git a/frontend/src/i18n/locales/ru.json b/frontend/src/i18n/locales/ru.json index 36336023..525fe10c 100644 --- a/frontend/src/i18n/locales/ru.json +++ b/frontend/src/i18n/locales/ru.json @@ -344,7 +344,8 @@ "emailLabel": "Электронная почта (необязательно)", "emailPlaceholder": "вы@пример.рф", "submit": "Продолжить", - "alreadyHere": "Я уже был здесь раньше" + "returningHint": "Уже были здесь? Ваш прежний выбор сохранён.", + "recoverPicks": "Вернуть выбор" }, "footer": { "forgetMeConfirm": "Ваше имя и выборки будут удалены из этой галереи.", diff --git a/frontend/src/i18n/locales/sl.json b/frontend/src/i18n/locales/sl.json index 2257fa7f..c63d4896 100644 --- a/frontend/src/i18n/locales/sl.json +++ b/frontend/src/i18n/locales/sl.json @@ -326,7 +326,8 @@ "emailLabel": "E-pošta (neobvezno)", "emailPlaceholder": "vi@example.com", "submit": "Nadaljuj", - "alreadyHere": "Tukaj sem že bil" + "returningHint": "Ste že bili tukaj? Vaša prejšnja izbira je še shranjena.", + "recoverPicks": "Prikliči izbiro" }, "footer": { "forgetMeConfirm": "Vaše ime in izbire bodo odstranjeni iz te galerije.",