diff --git a/frontend/rabbi_gerzi/cypress/e2e/about.cy.ts b/frontend/rabbi_gerzi/cypress/e2e/about.cy.ts index 94d6e38..be249f8 100644 --- a/frontend/rabbi_gerzi/cypress/e2e/about.cy.ts +++ b/frontend/rabbi_gerzi/cypress/e2e/about.cy.ts @@ -7,8 +7,8 @@ describe('about page', () => { cy.get('header.site-header').within(() => { cy.contains('Torah Media').should('be.visible') cy.contains('About').should('be.visible') + cy.contains('Contact').should('be.visible') cy.contains('Donate').should('be.visible') - cy.contains('a', 'Contact').should('not.exist') }) cy.get('[data-cy="about-hero"]').within(() => { @@ -108,18 +108,21 @@ describe('about page', () => { cy.get('[data-cy="haskamah-image"]').first().should('be.visible') }) - it('does not render the get involved subscription section', () => { - cy.get('[data-cy="get-involved"]').should('not.exist') - cy.contains('Subscribe for updates as we release new content') - .should('not.exist') - }) + it('renders the get involved form and footer', () => { + cy.get('[data-cy="get-involved"]').within(() => { + cy.contains('h2', 'Get Involved').should('be.visible') + cy.contains('Subscribe for updates as we release new content') + .should('be.visible') + cy.get('input[name="fullName"]').should('be.visible') + cy.get('input[name="email"]').should('be.visible') + cy.contains('button', 'Submit').should('be.visible') + }) - it('renders the footer with links and copyright', () => { cy.get('[data-cy="footer"]').within(() => { cy.contains('Torah Media').should('be.visible') cy.contains('About').should('be.visible') cy.contains('Donate').should('be.visible') - cy.contains('a', 'Contact').should('not.exist') + cy.contains('Contact').should('be.visible') cy.contains(/Rabbi Gerzi 2025/).should('be.visible') }) }) diff --git a/frontend/rabbi_gerzi/cypress/e2e/contact.cy.ts b/frontend/rabbi_gerzi/cypress/e2e/contact.cy.ts index 9465a9e..ad066b2 100644 --- a/frontend/rabbi_gerzi/cypress/e2e/contact.cy.ts +++ b/frontend/rabbi_gerzi/cypress/e2e/contact.cy.ts @@ -1,11 +1,119 @@ -describe('removed contact page', () => { - it('redirects direct contact visits to the homepage', () => { +describe('contact page', () => { + beforeEach(() => { cy.visit('/contact') + }) - cy.location('pathname').should('eq', '/') - cy.get('[data-cy="contact-hero"]').should('not.exist') + it('renders the header and contact intro', () => { cy.get('header.site-header').within(() => { - cy.contains('a', 'Contact').should('not.exist') + cy.contains('Torah Media').should('be.visible') + cy.contains('About').should('be.visible') + cy.contains('Contact').should('be.visible') + cy.contains('Donate').should('be.visible') + }) + + cy.get('[data-cy="contact-hero"]').within(() => { + cy.contains('h1', 'Contact').should('be.visible') + cy.contains('Rabbi Gerzi receives numerous messages each week') + .should('be.visible') + cy.contains('get back to you').should('be.visible') + }) + }) + + it('renders the copied message form fields', () => { + cy.get('[data-cy="contact-form"]').within(() => { + cy.contains('h2', 'Send Rabbi Gerzi a Message').should('be.visible') + cy.get('input[name="firstName"]').should('be.visible') + cy.get('input[name="lastName"]').should('be.visible') + cy.get('input[name="city"]').should('be.visible') + cy.get('input[name="country"]').should('be.visible') + cy.get('input[name="email"]') + .should('be.visible') + .and('have.attr', 'type', 'email') + cy.get('textarea[name="message"]').should('be.visible') + cy.contains('button', 'Submit').should('be.visible') + }) + }) + + it('clears the static contact form when submitted', () => { + cy.get('[data-cy="contact-form"]').within(() => { + cy.get('input[name="firstName"]').type('Yehuda') + cy.get('input[name="lastName"]').type('Cohen') + cy.get('input[name="city"]').type('Ramat Beit Shemesh') + cy.get('input[name="country"]').type('Israel') + cy.get('input[name="email"]').type('yehuda@example.com') + cy.get('textarea[name="message"]').type('Please send details.') + cy.contains('button', 'Submit').click() + + cy.get('input[name="firstName"]').should('have.value', '') + cy.get('input[name="lastName"]').should('have.value', '') + cy.get('input[name="city"]').should('have.value', '') + cy.get('input[name="country"]').should('have.value', '') + cy.get('input[name="email"]').should('have.value', '') + cy.get('textarea[name="message"]').should('have.value', '') + }) + }) + + it('renders social media links', () => { + cy.get('[data-cy="contact-social"]').within(() => { + cy.contains('h2', 'Follow Rabbi Gerzi on Social Media') + .should('be.visible') + cy.contains('a', 'YouTube') + .should('be.visible') + .and('have.attr', 'href', 'https://www.youtube.com/@rabbigerzi') + .and('have.attr', 'target', '_blank') + cy.contains('a', 'Spotify') + .should('be.visible') + .and( + 'have.attr', + 'href', + 'https://open.spotify.com/show/49H1X9A7KZWAnnIbcoQXQR?si=6253691f23be44ad', + ) + .and('have.attr', 'target', '_blank') + }) + }) + + it('keeps the page readable on mobile', () => { + cy.viewport(390, 900) + + cy.get('[data-cy="contact-hero"]').should('be.visible') + cy.get('[data-cy="contact-form"]').should('be.visible') + cy.get('[data-cy="contact-social"]').should('be.visible') + }) + + it('centers the subscribe card vertically in its section', () => { + cy.get('[data-cy="get-involved"]').then((sectionElements) => { + const sectionElement = sectionElements[0] + const cardElement = sectionElement.querySelector('.get-involved__card') + + if (cardElement === null) { + throw new Error('Get involved card is missing') + } + + const sectionBounds = sectionElement.getBoundingClientRect() + const cardBounds = cardElement.getBoundingClientRect() + const topBuffer = cardBounds.top - sectionBounds.top + const bottomBuffer = sectionBounds.bottom - cardBounds.bottom + + expect(topBuffer).to.be.closeTo(bottomBuffer, 1) + }) + }) + + it('renders the get involved form and footer', () => { + cy.get('[data-cy="get-involved"]').within(() => { + cy.contains('h2', 'Get Involved').should('be.visible') + cy.contains('Subscribe for updates as we release new content') + .should('be.visible') + cy.get('input[name="fullName"]').should('be.visible') + cy.get('input[name="newsletterEmail"]').should('be.visible') + cy.contains('button', 'Submit').should('be.visible') + }) + + cy.get('[data-cy="footer"]').within(() => { + cy.contains('Torah Media').should('be.visible') + cy.contains('About').should('be.visible') + cy.contains('Donate').should('be.visible') + cy.contains('Contact').should('be.visible') + cy.contains(/Rabbi Gerzi 2025/).should('be.visible') }) }) }) diff --git a/frontend/rabbi_gerzi/cypress/e2e/home.cy.ts b/frontend/rabbi_gerzi/cypress/e2e/home.cy.ts index 616b74c..e95938a 100644 --- a/frontend/rabbi_gerzi/cypress/e2e/home.cy.ts +++ b/frontend/rabbi_gerzi/cypress/e2e/home.cy.ts @@ -7,8 +7,8 @@ describe('homepage hero', () => { cy.get('header.site-header').within(() => { cy.contains('Torah Media').should('be.visible') cy.contains('About').should('be.visible') + cy.contains('Contact').should('be.visible') cy.contains('Donate').should('be.visible') - cy.contains('a', 'Contact').should('not.exist') }) }) @@ -29,8 +29,8 @@ describe('homepage hero', () => { cy.get('header.site-header').within(() => { cy.contains('Torah Media').should('be.visible') cy.contains('About').should('be.visible') + cy.contains('Contact').should('be.visible') cy.contains('Donate').should('be.visible') - cy.contains('a', 'Contact').should('not.exist') }) cy.contains('h1', 'Torah Media').should('be.visible') }) @@ -561,10 +561,16 @@ describe('homepage get involved and footer', () => { cy.visit('/') }) - it('does not render the newsletter signup section', () => { - cy.get('[data-cy="get-involved"]').should('not.exist') - cy.contains('Subscribe for updates as we release new content') - .should('not.exist') + it('renders the newsletter signup form', () => { + cy.get('[data-cy="get-involved"]').within(() => { + cy.contains('h2', 'Get Involved').should('be.visible') + cy.contains( + 'Subscribe for updates as we release new content', + ).should('be.visible') + cy.get('input[name="fullName"]').should('be.visible') + cy.get('input[name="email"]').should('be.visible') + cy.contains('button', 'Submit').should('be.visible') + }) }) it('renders the footer with links and copyright', () => { @@ -572,7 +578,7 @@ describe('homepage get involved and footer', () => { cy.contains('Torah Media').should('be.visible') cy.contains('About').should('be.visible') cy.contains('Donate').should('be.visible') - cy.contains('a', 'Contact').should('not.exist') + cy.contains('Contact').should('be.visible') cy.contains(/Rabbi Gerzi 2025/).should('be.visible') }) }) diff --git a/frontend/rabbi_gerzi/cypress/e2e/media.cy.ts b/frontend/rabbi_gerzi/cypress/e2e/media.cy.ts index 2bbb833..46d81a1 100644 --- a/frontend/rabbi_gerzi/cypress/e2e/media.cy.ts +++ b/frontend/rabbi_gerzi/cypress/e2e/media.cy.ts @@ -19,8 +19,8 @@ describe('media page sets', () => { cy.get('header.site-header').within(() => { cy.contains('Torah Media').should('be.visible') cy.contains('About').should('be.visible') + cy.contains('Contact').should('be.visible') cy.contains('Donate').should('be.visible') - cy.contains('a', 'Contact').should('not.exist') }) cy.contains('h1', 'Torah Media').should('be.visible') cy.get('[data-cy="media-set-grid"]').should('be.visible') diff --git a/frontend/rabbi_gerzi/src/components/SiteFooter.vue b/frontend/rabbi_gerzi/src/components/SiteFooter.vue index 556f5a0..f2ea224 100644 --- a/frontend/rabbi_gerzi/src/components/SiteFooter.vue +++ b/frontend/rabbi_gerzi/src/components/SiteFooter.vue @@ -46,6 +46,7 @@ const footerLinkColumns: FooterLink[][] = [ label: 'Donate', linkAttributes: donateLinkAttributes, }, + { kind: 'route', label: 'Contact', to: '/contact' }, ], ] diff --git a/frontend/rabbi_gerzi/src/components/SiteHeader.vue b/frontend/rabbi_gerzi/src/components/SiteHeader.vue index d755c95..34095b1 100644 --- a/frontend/rabbi_gerzi/src/components/SiteHeader.vue +++ b/frontend/rabbi_gerzi/src/components/SiteHeader.vue @@ -37,6 +37,7 @@ async function handleLogout(): Promise { const navItems: InternalHeaderNavItem[] = [ { label: 'Torah Media', href: '/media', icon: '▶' }, { label: 'About', href: '/about', icon: 'ⓘ' }, + { label: 'Contact', href: '/contact', icon: '➤' }, ] const donateNavItem: HeaderNavItem = { diff --git a/frontend/rabbi_gerzi/src/router/index.ts b/frontend/rabbi_gerzi/src/router/index.ts index 9630585..2ea2c76 100644 --- a/frontend/rabbi_gerzi/src/router/index.ts +++ b/frontend/rabbi_gerzi/src/router/index.ts @@ -1,6 +1,7 @@ import { createRouter, createWebHistory } from 'vue-router' import HomePage from '@/views/HomePage.vue' import AboutPage from '@/views/AboutPage.vue' +import ContactPage from '@/views/ContactPage.vue' import LoginPage from '@/views/LoginPage.vue' import MediaPage from '@/views/MediaPage.vue' import ElementPage from '@/views/ElementPage.vue' @@ -27,7 +28,8 @@ const router = createRouter({ }, { path: '/contact', - redirect: { name: 'home' }, + name: 'contact', + component: ContactPage, }, { path: '/media', diff --git a/frontend/rabbi_gerzi/src/views/AboutPage.vue b/frontend/rabbi_gerzi/src/views/AboutPage.vue index e1b35c0..10b4474 100644 --- a/frontend/rabbi_gerzi/src/views/AboutPage.vue +++ b/frontend/rabbi_gerzi/src/views/AboutPage.vue @@ -1,4 +1,5 @@ @@ -1061,6 +1106,89 @@ function projectCardStyle(project: Project): string { max-width: 30ch; } +.get-involved { + background: var(--color-cream); + padding: 0 2rem 5rem; +} + +.get-involved__card { + max-width: 1100px; + margin: 0 auto; + background: rgba(255, 255, 255, 0.4); + border-radius: 14px; + padding: 3rem; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 3rem; + align-items: center; +} + +.get-involved__heading { + font-family: var(--font-serif); + font-weight: 400; + font-size: clamp(1.6rem, 2.6vw, 2.4rem); + color: var(--color-text); + margin: 0 0 1.25rem 0; +} + +.get-involved__subtext { + font-family: var(--font-serif); + font-size: 1rem; + line-height: 1.55; + color: var(--color-text-muted); + margin: 0; + max-width: 36ch; +} + +.get-involved__form { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.get-involved__field { + display: flex; + flex-direction: column; + gap: 0.4rem; +} + +.get-involved__label { + font-family: var(--font-sans); + font-size: 0.85rem; + color: var(--color-text-muted); +} + +.get-involved__input { + background: var(--color-cream); + border: 1px solid rgba(0, 0, 0, 0.08); + border-radius: 8px; + padding: 0.75rem 1rem; + font-family: var(--font-sans); + font-size: 1rem; + color: var(--color-text); +} + +.get-involved__input:focus { + outline: 2px solid var(--color-slate); + outline-offset: 2px; +} + +.get-involved__submit { + background: var(--color-text); + color: var(--color-white); + border: none; + border-radius: 8px; + padding: 0.85rem 1rem; + font-family: var(--font-sans); + font-weight: 500; + font-size: 1rem; + margin-top: 0.5rem; +} + +.get-involved__submit:hover { + background: var(--color-slate); +} + @media (max-width: 768px) { .testimonials { --testimonials-card-height: 320px; @@ -1122,6 +1250,12 @@ function projectCardStyle(project: Project): string { .journey__cards { grid-template-columns: 1fr; } + + .get-involved__card { + grid-template-columns: 1fr; + padding: 2rem; + gap: 2rem; + } } @media (max-width: 480px) {