diff --git a/backend/app/Controllers/ElementController.php b/backend/app/Controllers/ElementController.php index 5164e59..36528d4 100644 --- a/backend/app/Controllers/ElementController.php +++ b/backend/app/Controllers/ElementController.php @@ -46,18 +46,15 @@ class ElementController } $element = $result->getElement(); - $childElements = []; - foreach ($result->getChildElements() as $childElement) { - $childElements[] = [ - 'id' => $childElement->getId(), - 'title' => $childElement->getTitle(), - 'description' => $childElement->getDescription(), - ]; - } return new JsonResponse([ - 'childElements' => $childElements, + 'childElements' => $this->buildElementSummaryPayloads( + $result->getChildElements(), + ), 'element' => $this->buildElementPayload($element), + 'siblingElements' => $this->buildElementSummaryPayloads( + $result->getSiblingElements(), + ), ], 200); } @@ -253,6 +250,28 @@ class ElementController ]; } + /** + * @param Element[] $elements + * @return array + */ + private function buildElementSummaryPayloads(array $elements): array + { + $payloads = []; + foreach ($elements as $element) { + $payloads[] = [ + 'id' => $element->getId(), + 'title' => $element->getTitle(), + 'description' => $element->getDescription(), + ]; + } + + return $payloads; + } + private function storageUrl(?string $path, string $folderPrefix): ?string { if ($path === null) { diff --git a/backend/app/Element/UseCases/GetElement/GetElement.php b/backend/app/Element/UseCases/GetElement/GetElement.php index 7103e47..584d30b 100644 --- a/backend/app/Element/UseCases/GetElement/GetElement.php +++ b/backend/app/Element/UseCases/GetElement/GetElement.php @@ -27,10 +27,19 @@ class GetElement throw new NotFoundException('Element not found'); } + $parentElement = $element->getParentElement(); + if ($parentElement === null) { + $siblingElements = [$element]; + } else { + $siblingElements = $this->elementRepository + ->findByParentElement($parentElement); + } + return new GetElementResult( element: $element, childElements: $this->elementRepository ->findByParentElement($element), + siblingElements: $siblingElements, ); } } diff --git a/backend/app/Element/UseCases/GetElement/GetElementResult.php b/backend/app/Element/UseCases/GetElement/GetElementResult.php index ad5f51b..6934411 100644 --- a/backend/app/Element/UseCases/GetElement/GetElementResult.php +++ b/backend/app/Element/UseCases/GetElement/GetElementResult.php @@ -8,10 +8,12 @@ class GetElementResult { /** * @param Element[] $childElements + * @param Element[] $siblingElements */ public function __construct( private Element $element, private array $childElements, + private array $siblingElements, ) { } @@ -27,4 +29,12 @@ class GetElementResult { return $this->childElements; } + + /** + * @return Element[] + */ + public function getSiblingElements(): array + { + return $this->siblingElements; + } } diff --git a/backend/tests/Feature/ElementsEndpointTest.php b/backend/tests/Feature/ElementsEndpointTest.php index 157f944..a914dd9 100644 --- a/backend/tests/Feature/ElementsEndpointTest.php +++ b/backend/tests/Feature/ElementsEndpointTest.php @@ -94,6 +94,74 @@ class ElementsEndpointTest extends TestCase 'longPdfPath' => '/assets/pdfs/baderech-long.pdf', 'youtubeUrl' => $sampleYoutubeUrl, ], + 'siblingElements' => [ + [ + 'id' => $element->getId(), + 'title' => 'Baderech HaAvodah', + 'description' => 'A structured path for growth', + ], + ], + ]); + } + + public function testReturnsSameParentSiblingElements(): void + { + $setRepository = app(SetRepository::class); + $elementRepository = app(ElementRepository::class); + $set = $this->createSet($setRepository); + $parentElement = $this->createElement( + $elementRepository, + $set, + 'Baderech HaAvodah', + 'A structured path for growth', + null, + '', + null, + null, + null, + null, + ); + $firstChildElement = $this->createElement( + $elementRepository, + $set, + 'Avodah Foundations', + 'Foundations for steady avodah', + null, + '

Foundations rich text

', + null, + null, + null, + $parentElement, + ); + $secondChildElement = $this->createElement( + $elementRepository, + $set, + 'Daily Practice', + 'Daily practices for growth', + null, + '', + null, + null, + null, + $parentElement, + ); + + $response = $this->getJson( + "/api/elements/{$firstChildElement->getId()}" + ); + + $response->assertOk(); + $response->assertJsonPath('siblingElements', [ + [ + 'id' => $firstChildElement->getId(), + 'title' => 'Avodah Foundations', + 'description' => 'Foundations for steady avodah', + ], + [ + 'id' => $secondChildElement->getId(), + 'title' => 'Daily Practice', + 'description' => 'Daily practices for growth', + ], ]); } diff --git a/backend/tests/Unit/Controllers/ElementControllerTest.php b/backend/tests/Unit/Controllers/ElementControllerTest.php index c0ce0a6..c32ac21 100644 --- a/backend/tests/Unit/Controllers/ElementControllerTest.php +++ b/backend/tests/Unit/Controllers/ElementControllerTest.php @@ -150,6 +150,68 @@ class ElementControllerTest extends TestCase 'description' => 'Daily practices for growth', ], ], $body['childElements']); + $this->assertSame([ + [ + 'id' => $element->getId(), + 'title' => 'Baderech HaAvodah', + 'description' => 'A structured path for growth', + ], + ], $body['siblingElements']); + } + + public function testShowReturnsSameParentSiblingPayload(): void + { + $set = $this->createSet(1, 'Baderech'); + $parentElement = $this->createElement( + $set, + 'Baderech HaAvodah', + 'A structured path for growth', + null, + '', + null, + null, + null, + null, + ); + $firstChildElement = $this->createElement( + $set, + 'Avodah Foundations', + 'Foundations for steady avodah', + null, + '

Foundations rich text

', + null, + null, + null, + $parentElement, + ); + $secondChildElement = $this->createElement( + $set, + 'Daily Practice', + 'Daily practices for growth', + null, + '', + null, + null, + null, + $parentElement, + ); + + $response = $this->controller->show($firstChildElement->getId()); + + $this->assertEquals(200, $response->getStatusCode()); + $body = json_decode($response->getContent(), true); + $this->assertSame([ + [ + 'id' => $firstChildElement->getId(), + 'title' => 'Avodah Foundations', + 'description' => 'Foundations for steady avodah', + ], + [ + 'id' => $secondChildElement->getId(), + 'title' => 'Daily Practice', + 'description' => 'Daily practices for growth', + ], + ], $body['siblingElements']); } public function testShowReturns400WhenIdMissing(): void diff --git a/backend/tests/Unit/Element/UseCases/GetElementTest.php b/backend/tests/Unit/Element/UseCases/GetElementTest.php index d4053f5..69917bd 100644 --- a/backend/tests/Unit/Element/UseCases/GetElementTest.php +++ b/backend/tests/Unit/Element/UseCases/GetElementTest.php @@ -159,6 +159,134 @@ class GetElementTest extends TestCase ); } + public function testReturnsSiblingElementsWithSameParent(): void + { + $set = $this->createSet(1, 'Baderech'); + $parentElement = $this->createElement( + $set, + 'Baderech HaAvodah', + 'A structured path for growth', + null, + '

A structured path for growth

', + '/assets/pdfs/baderech.pdf', + null, + null, + ); + $firstChildElement = $this->createElement( + $set, + 'Avodah Foundations', + 'Foundations for steady avodah', + null, + '

Foundations rich text

', + '/assets/pdfs/foundations.pdf', + null, + $parentElement, + ); + $secondChildElement = $this->createElement( + $set, + 'Daily Practice', + 'Daily practices for growth', + null, + '

Daily practice rich text

', + null, + null, + $parentElement, + ); + $this->createElement( + $set, + 'Nested Practice', + 'Nested description', + null, + '

Nested rich text

', + null, + null, + $firstChildElement, + ); + $otherSet = $this->createSet(2, 'Daily Learning'); + $otherParentElement = $this->createElement( + $otherSet, + 'Other Parent', + 'Other parent description', + null, + '

Other parent rich text

', + null, + null, + null, + ); + $this->createElement( + $otherSet, + 'Other Child', + 'Other child description', + null, + '

Other child rich text

', + null, + null, + $otherParentElement, + ); + + $result = $this->getElement->execute(new GetElementRequest( + id: $firstChildElement->getId(), + )); + $siblingElements = $result->getSiblingElements(); + + $this->assertCount(2, $siblingElements); + $this->assertSame( + $firstChildElement->getId(), + $siblingElements[0]->getId(), + ); + $this->assertSame( + 'Avodah Foundations', + $siblingElements[0]->getTitle(), + ); + $this->assertSame( + 'Foundations for steady avodah', + $siblingElements[0]->getDescription(), + ); + $this->assertSame( + $secondChildElement->getId(), + $siblingElements[1]->getId(), + ); + $this->assertSame('Daily Practice', $siblingElements[1]->getTitle()); + $this->assertSame( + 'Daily practices for growth', + $siblingElements[1]->getDescription(), + ); + } + + public function testReturnsRootElementAsOnlySibling(): void + { + $set = $this->createSet(1, 'Baderech'); + $rootElement = $this->createElement( + $set, + 'Baderech HaAvodah', + 'A structured path for growth', + null, + '

A structured path for growth

', + '/assets/pdfs/baderech.pdf', + null, + null, + ); + $this->createElement( + $set, + 'Avodah Foundations', + 'Foundations for steady avodah', + null, + '

Foundations rich text

', + '/assets/pdfs/foundations.pdf', + null, + $rootElement, + ); + + $result = $this->getElement->execute(new GetElementRequest( + id: $rootElement->getId(), + )); + $siblingElements = $result->getSiblingElements(); + + $this->assertCount(1, $siblingElements); + $this->assertSame($rootElement->getId(), $siblingElements[0]->getId()); + $this->assertSame('Baderech HaAvodah', $siblingElements[0]->getTitle()); + } + public function testThrowsWhenIdMissing(): void { $this->expectException(BadRequestException::class); diff --git a/frontend/rabbi_gerzi/.editorconfig b/frontend/rabbi_gerzi/.editorconfig index 3b510aa..d720b4b 100644 --- a/frontend/rabbi_gerzi/.editorconfig +++ b/frontend/rabbi_gerzi/.editorconfig @@ -5,4 +5,4 @@ indent_style = space insert_final_newline = true trim_trailing_whitespace = true end_of_line = lf -max_line_length = 100 +max_line_length = 80 diff --git a/frontend/rabbi_gerzi/.oxfmtrc.json b/frontend/rabbi_gerzi/.oxfmtrc.json index 4bddb67..977ee22 100644 --- a/frontend/rabbi_gerzi/.oxfmtrc.json +++ b/frontend/rabbi_gerzi/.oxfmtrc.json @@ -1,5 +1,6 @@ { "$schema": "./node_modules/oxfmt/configuration_schema.json", + "printWidth": 80, "semi": false, "singleQuote": true } diff --git a/frontend/rabbi_gerzi/cypress/e2e/element-sibling-navigation.cy.ts b/frontend/rabbi_gerzi/cypress/e2e/element-sibling-navigation.cy.ts new file mode 100644 index 0000000..8c52491 --- /dev/null +++ b/frontend/rabbi_gerzi/cypress/e2e/element-sibling-navigation.cy.ts @@ -0,0 +1,95 @@ +function loginAsAdmin(): void { + cy.visit('/login') + + cy.get('[data-cy="login-email"]').type('admin@rabbigerzi.test') + cy.get('[data-cy="login-password"]').type('password123!@#') + cy.get('[data-cy="login-submit"]').click() + + cy.url().should('eq', Cypress.config().baseUrl + '/') + cy.getCookie('auth_token').should('exist') +} + +describe('element sibling navigation', () => { + it('navigates public same-parent elements with wraparound', () => { + cy.visit('/element/1') + cy.contains('h1', 'Baderech HaAvodah').should('be.visible') + cy.get('[data-cy="element-sibling-navigation"]').should('not.exist') + + cy.visit('/element/2') + cy.contains('h1', '1. Introduction').should('be.visible') + cy.get('[data-cy="child-element-list"]').then((childList) => { + cy.get('[data-cy="element-sibling-navigation"]').then( + (siblingNavigation) => { + expect( + childList[0].compareDocumentPosition(siblingNavigation[0]) + & Node.DOCUMENT_POSITION_FOLLOWING, + ).to.not.equal(0) + }, + ) + }) + cy.get('[data-cy="element-sibling-navigation"]').should('be.visible') + cy.get('[data-cy="element-sibling-previous"]') + .should('have.attr', 'href', '/element/7') + .click() + + cy.location('pathname').should('eq', '/element/7') + cy.contains('h1', '6. Fluid Integration').should('be.visible') + cy.get('[data-cy="element-sibling-next"]') + .should('have.attr', 'href', '/element/2') + .click() + + cy.location('pathname').should('eq', '/element/2') + cy.contains('h1', '1. Introduction').should('be.visible') + cy.get('[data-cy="element-sibling-next"]') + .should('have.attr', 'href', '/element/3') + .click() + + cy.location('pathname').should('eq', '/element/3') + cy.contains('h1', '2. Foundations').should('be.visible') + }) + + it('navigates admin same-parent elements with wraparound', () => { + loginAsAdmin() + + cy.visit('/admin/element/1') + cy.get('[data-cy="admin-element-title"]') + .should('have.value', 'Baderech HaAvodah') + cy.get('[data-cy="admin-sibling-navigation"]').should('not.exist') + + cy.visit('/admin/element/2') + cy.get('[data-cy="admin-element-title"]') + .should('have.value', '1. Introduction') + cy.get('[data-cy="admin-element-save"]').then((saveButton) => { + cy.get('[data-cy="admin-sibling-navigation"]').then( + (siblingNavigation) => { + expect( + saveButton[0].compareDocumentPosition(siblingNavigation[0]) + & Node.DOCUMENT_POSITION_FOLLOWING, + ).to.not.equal(0) + }, + ) + }) + cy.get('[data-cy="admin-sibling-navigation"]').should('be.visible') + cy.get('[data-cy="admin-sibling-previous"]') + .should('have.attr', 'href', '/admin/element/7') + .click() + + cy.location('pathname').should('eq', '/admin/element/7') + cy.get('[data-cy="admin-element-title"]') + .should('have.value', '6. Fluid Integration') + cy.get('[data-cy="admin-sibling-next"]') + .should('have.attr', 'href', '/admin/element/2') + .click() + + cy.location('pathname').should('eq', '/admin/element/2') + cy.get('[data-cy="admin-element-title"]') + .should('have.value', '1. Introduction') + cy.get('[data-cy="admin-sibling-next"]') + .should('have.attr', 'href', '/admin/element/3') + .click() + + cy.location('pathname').should('eq', '/admin/element/3') + cy.get('[data-cy="admin-element-title"]') + .should('have.value', '2. Foundations') + }) +}) diff --git a/frontend/rabbi_gerzi/src/components/ElementSiblingNavigation.vue b/frontend/rabbi_gerzi/src/components/ElementSiblingNavigation.vue new file mode 100644 index 0000000..7eaa976 --- /dev/null +++ b/frontend/rabbi_gerzi/src/components/ElementSiblingNavigation.vue @@ -0,0 +1,138 @@ + + + + + diff --git a/frontend/rabbi_gerzi/src/stores/auth.ts b/frontend/rabbi_gerzi/src/stores/auth.ts index c6e9bc8..65dee47 100644 --- a/frontend/rabbi_gerzi/src/stores/auth.ts +++ b/frontend/rabbi_gerzi/src/stores/auth.ts @@ -73,5 +73,13 @@ export const useAuthStore = defineStore('auth', () => { } } - return { user, loginError, isSubmitting, isAuthenticated, login, fetchUser, logout } + return { + user, + loginError, + isSubmitting, + isAuthenticated, + login, + fetchUser, + logout, + } }) diff --git a/frontend/rabbi_gerzi/src/stores/elements.ts b/frontend/rabbi_gerzi/src/stores/elements.ts index d72683c..5118f39 100644 --- a/frontend/rabbi_gerzi/src/stores/elements.ts +++ b/frontend/rabbi_gerzi/src/stores/elements.ts @@ -20,6 +20,7 @@ type ElementFileType = 'iconImage' | 'shortPdf' | 'longPdf' interface ElementResponse { element: Element childElements: ChildElement[] + siblingElements: ChildElement[] } export interface UpdateElementInput { @@ -58,6 +59,7 @@ const ELEMENT_UPDATE_URL = `${API_BASE_URL}/api/element/update` export const useElementsStore = defineStore('elements', () => { const element = ref(null) const childElements = ref([]) + const siblingElements = ref([]) const isLoading = ref(false) const error = ref(null) const isSaving = ref(false) @@ -71,6 +73,7 @@ export const useElementsStore = defineStore('elements', () => { async function fetchElement(elementId: string): Promise { element.value = null childElements.value = [] + siblingElements.value = [] error.value = null saveError.value = null childActionError.value = null @@ -94,8 +97,10 @@ export const useElementsStore = defineStore('elements', () => { const data: ElementResponse = await response.json() element.value = data.element childElements.value = data.childElements + siblingElements.value = data.siblingElements } catch { childElements.value = [] + siblingElements.value = [] error.value = 'Network error - could not load element' } finally { isLoading.value = false @@ -153,8 +158,8 @@ export const useElementsStore = defineStore('elements', () => { childElementId.toString(), ) const response = await fetch( - `${ELEMENTS_URL}/${encodedParentElementId}` - + `/children/${encodedChildElementId}`, + `${ELEMENTS_URL}/${encodedParentElementId}` + + `/children/${encodedChildElementId}`, { method: 'DELETE', credentials: 'include', @@ -388,6 +393,7 @@ export const useElementsStore = defineStore('elements', () => { return { element, childElements, + siblingElements, isLoading, error, isSaving, diff --git a/frontend/rabbi_gerzi/src/views/AboutPage.vue b/frontend/rabbi_gerzi/src/views/AboutPage.vue index d25a7f4..10b4474 100644 --- a/frontend/rabbi_gerzi/src/views/AboutPage.vue +++ b/frontend/rabbi_gerzi/src/views/AboutPage.vue @@ -73,7 +73,8 @@ const teachers: Teacher[] = [ }, { name: 'Rabbi Efraim Greenblatt', - description: 'Halachic authority and loyal student of Rabbi Moshe Feinstein.', + description: + 'Halachic authority and loyal student of Rabbi Moshe Feinstein.', }, { name: 'The Sassover Rebbe, The Biala Rebbe, and Rabbi Mordechai Zukerman', @@ -230,8 +231,8 @@ function submitNewsletter(submitEvent: Event): void {

Pilzno Institute

About Rabbi Gerzi

- Healthy, Integrated and Balanced Torah living, rooted in mesorah and made practical - for real people building real lives. + Healthy, Integrated and Balanced Torah living, rooted in mesorah + and made practical for real people building real lives.

-
+

Vision

Rabbi Gerzi's Vision

- Rabbi Gerzi guides individuals from vagueness to clarity, from surviving to thriving, - from a galut to a geula mindset. Through profound teachings rooted in Torah wisdom, - Rabbi Gerzi aims to connect students with their divine purpose, discover balance, and + Rabbi Gerzi guides individuals from vagueness to clarity, from + surviving to thriving, from a galut to a geula mindset. Through + profound teachings rooted in Torah wisdom, Rabbi Gerzi aims to + connect students with their divine purpose, discover balance, and live a life of authentic joy and fulfillment.

- As spiritual growth can only be accomplished within a fertile social framework, Rabbi - Gerzi hosts regular weekly chaburot and various events that create the conditions for - authentic life transformation. + As spiritual growth can only be accomplished within a fertile social + framework, Rabbi Gerzi hosts regular weekly chaburot and various + events that create the conditions for authentic life transformation.

-
+

Mission

@@ -287,9 +295,10 @@ function submitNewsletter(submitEvent: Event): void { The Mission: To Provide a Clear Path to Wholesome Living

- Rabbi Gerzi provides a structured path to wisdom, personal growth, and transformation. - His educational system is designed to guide individuals through essential life areas, - creating not just intellectual understanding but genuine change. + Rabbi Gerzi provides a structured path to wisdom, personal growth, + and transformation. His educational system is designed to guide + individuals through essential life areas, creating not just + intellectual understanding but genuine change.

@@ -318,27 +327,35 @@ function submitNewsletter(submitEvent: Event): void {

- Rabbi Gerzi's teachings are deeply influenced by his mentor, Rabbi Yosef Singer, the - Pilzno Rav. Under Rabbi Singer's guidance, Rabbi Gerzi absorbed the teachings of the - Baal Shem Tov, Chassidut, Kabbalah, and Halacha. + Rabbi Gerzi's teachings are deeply influenced by his mentor, Rabbi + Yosef Singer, the Pilzno Rav. Under Rabbi Singer's guidance, Rabbi + Gerzi absorbed the teachings of the Baal Shem Tov, Chassidut, + Kabbalah, and Halacha.

- In 2005, the Pilzno Rav instructed Rabbi Gerzi to continue his legacy by building a - community in Eretz Yisrael dedicated to this unique form of Healthy, Integrated and - Balanced Torah living. + In 2005, the Pilzno Rav instructed Rabbi Gerzi to continue his + legacy by building a community in Eretz Yisrael dedicated to this + unique form of Healthy, Integrated and Balanced Torah living.

-
+

Teachers

Rabbi Gerzi's Teachers

    -
  1. +
  2. {{ teacher.name }} {{ teacher.description }} @@ -397,7 +414,8 @@ function submitNewsletter(submitEvent: Event): void {

    Get Involved

    - Subscribe for updates as we release new content, chaburot and events. + Subscribe for updates as we release new content, chaburot and + events.

    diff --git a/frontend/rabbi_gerzi/src/views/AdminElementPage.vue b/frontend/rabbi_gerzi/src/views/AdminElementPage.vue index 75b257a..29f66fe 100644 --- a/frontend/rabbi_gerzi/src/views/AdminElementPage.vue +++ b/frontend/rabbi_gerzi/src/views/AdminElementPage.vue @@ -2,6 +2,7 @@ import { storeToRefs } from 'pinia' import { computed, reactive, ref, watch } from 'vue' import { useRoute, useRouter } from 'vue-router' +import ElementSiblingNavigation from '@/components/ElementSiblingNavigation.vue' import RichTextEditor from '@/components/RichTextEditor.vue' import SiteHeader from '@/components/SiteHeader.vue' import { useElementsStore } from '@/stores/elements' @@ -23,6 +24,7 @@ const elementsStore = useElementsStore() const { element, childElements, + siblingElements, isLoading, error, isSaving, @@ -617,6 +619,13 @@ function resetInput(changeEvent: Event): void { {{ statusMessage }}

+ + diff --git a/frontend/rabbi_gerzi/src/views/ContactPage.vue b/frontend/rabbi_gerzi/src/views/ContactPage.vue index 372dd29..21f71cf 100644 --- a/frontend/rabbi_gerzi/src/views/ContactPage.vue +++ b/frontend/rabbi_gerzi/src/views/ContactPage.vue @@ -40,15 +40,20 @@ function submitNewsletter(submitEvent: Event): void {

Contact

Contact

- Rabbi Gerzi receives numerous messages each week. It may take some time to get back to - you, but we will do our best to respond in a timely manner. + Rabbi Gerzi receives numerous messages each week. It may take some + time to get back to you, but we will do our best to respond in a + timely manner.

diff --git a/frontend/rabbi_gerzi/src/views/HomePage.vue b/frontend/rabbi_gerzi/src/views/HomePage.vue index 3f4684a..a2bd66a 100644 --- a/frontend/rabbi_gerzi/src/views/HomePage.vue +++ b/frontend/rabbi_gerzi/src/views/HomePage.vue @@ -42,7 +42,9 @@ const testimonials: Testimonial[] = [ }, { name: 'Anonymous Student', - quote: 'My understanding of Torah has been profoundly improved through ' + 'his teachings.', + quote: + 'My understanding of Torah has been profoundly improved through ' + + 'his teachings.', }, { name: 'Placeholder Student 1', @@ -267,7 +269,8 @@ function projectCardStyle(project: Project): string {

Let’s upgrade our lives to a - healthy, integrated, and balanced Torah existence. + healthy, integrated, and balanced Torah + existence.

Rabbi Yehoshua Gerzi

@@ -297,23 +300,29 @@ function projectCardStyle(project: Project): string { -

Discover a Path to a Holistically Engaged Life

+

+ Discover a Path to a Holistically Engaged Life +

- Rabbi Yehoshua Gerzi has dedicated his life to empowering individuals to achieve a state - of “Healthy, Integrated and Balanced Torah Living.” His teachings weave - together timeless Torah wisdom, mystical insights and practical guidance, making ancient - wisdom accessible and transformative for modern lives. + Rabbi Yehoshua Gerzi has dedicated his life to empowering individuals + to achieve a state of “Healthy, Integrated and Balanced Torah + Living.” His teachings weave together timeless Torah wisdom, + mystical insights and practical guidance, making ancient wisdom + accessible and transformative for modern lives.

- Rabbi Gerzi is based in Ramat Beit Shemesh, where he serves as Rav of Kehillas Beis David - and Director of the Pilzno Institute of Higher Learning. + Rabbi Gerzi is based in Ramat Beit Shemesh, where he serves as Rav of + Kehillas Beis David and Director of the Pilzno Institute of Higher + Learning.

-

Baderech HaAvodah (Core Teachings)

+

+ Baderech HaAvodah (Core Teachings) +

@@ -348,13 +359,18 @@ function projectCardStyle(project: Project): string {

A Unique Voice for the Generation

- Rabbi Gerzi provides a wholly unique and much needed perspective to his students. Carrying - decades of experience guided by a handful of the generations most pre-eminent Rabbanim, he - teaches a message of practically engaged positivity. His Torah is one of unity, bridging - hashkafic boundaries and engaging a diverse cadre of Jews who are open minded to embracing - meaningful and experiential Judaism. + Rabbi Gerzi provides a wholly unique and much needed perspective to + his students. Carrying decades of experience guided by a handful of + the generations most pre-eminent Rabbanim, he teaches a message of + practically engaged positivity. His Torah is one of unity, bridging + hashkafic boundaries and engaging a diverse cadre of Jews who are open + minded to embracing meaningful and experiential Judaism.

- @@ -373,7 +389,9 @@ function projectCardStyle(project: Project): string { :aria-hidden="testimonialPosition(index) !== 'active'" >

{{ testimonial.name }}

-

“{{ testimonial.quote }}”

+

+ “{{ testimonial.quote }}” +