Compare commits

..

No commits in common. "14de828b4b37887bb98feaa53ade27c7ddf4b60b" and "90d28deffb2ed1425bb81691ac21a36b183906d3" have entirely different histories.

18 changed files with 65 additions and 684 deletions

View file

@ -46,15 +46,18 @@ class ElementController
} }
$element = $result->getElement(); $element = $result->getElement();
$childElements = [];
foreach ($result->getChildElements() as $childElement) {
$childElements[] = [
'id' => $childElement->getId(),
'title' => $childElement->getTitle(),
'description' => $childElement->getDescription(),
];
}
return new JsonResponse([ return new JsonResponse([
'childElements' => $this->buildElementSummaryPayloads( 'childElements' => $childElements,
$result->getChildElements(),
),
'element' => $this->buildElementPayload($element), 'element' => $this->buildElementPayload($element),
'siblingElements' => $this->buildElementSummaryPayloads(
$result->getSiblingElements(),
),
], 200); ], 200);
} }
@ -250,28 +253,6 @@ class ElementController
]; ];
} }
/**
* @param Element[] $elements
* @return array<int, array{
* id: int,
* title: string,
* description: string,
* }>
*/
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 private function storageUrl(?string $path, string $folderPrefix): ?string
{ {
if ($path === null) { if ($path === null) {

View file

@ -27,19 +27,10 @@ class GetElement
throw new NotFoundException('Element not found'); throw new NotFoundException('Element not found');
} }
$parentElement = $element->getParentElement();
if ($parentElement === null) {
$siblingElements = [$element];
} else {
$siblingElements = $this->elementRepository
->findByParentElement($parentElement);
}
return new GetElementResult( return new GetElementResult(
element: $element, element: $element,
childElements: $this->elementRepository childElements: $this->elementRepository
->findByParentElement($element), ->findByParentElement($element),
siblingElements: $siblingElements,
); );
} }
} }

View file

@ -8,12 +8,10 @@ class GetElementResult
{ {
/** /**
* @param Element[] $childElements * @param Element[] $childElements
* @param Element[] $siblingElements
*/ */
public function __construct( public function __construct(
private Element $element, private Element $element,
private array $childElements, private array $childElements,
private array $siblingElements,
) { ) {
} }
@ -29,12 +27,4 @@ class GetElementResult
{ {
return $this->childElements; return $this->childElements;
} }
/**
* @return Element[]
*/
public function getSiblingElements(): array
{
return $this->siblingElements;
}
} }

View file

@ -94,74 +94,6 @@ class ElementsEndpointTest extends TestCase
'longPdfPath' => '/assets/pdfs/baderech-long.pdf', 'longPdfPath' => '/assets/pdfs/baderech-long.pdf',
'youtubeUrl' => $sampleYoutubeUrl, '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,
'<p>Foundations rich text</p>',
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',
],
]); ]);
} }

View file

@ -150,68 +150,6 @@ class ElementControllerTest extends TestCase
'description' => 'Daily practices for growth', 'description' => 'Daily practices for growth',
], ],
], $body['childElements']); ], $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,
'<p>Foundations rich text</p>',
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 public function testShowReturns400WhenIdMissing(): void

View file

@ -159,134 +159,6 @@ 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,
'<p>A structured path for growth</p>',
'/assets/pdfs/baderech.pdf',
null,
null,
);
$firstChildElement = $this->createElement(
$set,
'Avodah Foundations',
'Foundations for steady avodah',
null,
'<p>Foundations rich text</p>',
'/assets/pdfs/foundations.pdf',
null,
$parentElement,
);
$secondChildElement = $this->createElement(
$set,
'Daily Practice',
'Daily practices for growth',
null,
'<p>Daily practice rich text</p>',
null,
null,
$parentElement,
);
$this->createElement(
$set,
'Nested Practice',
'Nested description',
null,
'<p>Nested rich text</p>',
null,
null,
$firstChildElement,
);
$otherSet = $this->createSet(2, 'Daily Learning');
$otherParentElement = $this->createElement(
$otherSet,
'Other Parent',
'Other parent description',
null,
'<p>Other parent rich text</p>',
null,
null,
null,
);
$this->createElement(
$otherSet,
'Other Child',
'Other child description',
null,
'<p>Other child rich text</p>',
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,
'<p>A structured path for growth</p>',
'/assets/pdfs/baderech.pdf',
null,
null,
);
$this->createElement(
$set,
'Avodah Foundations',
'Foundations for steady avodah',
null,
'<p>Foundations rich text</p>',
'/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 public function testThrowsWhenIdMissing(): void
{ {
$this->expectException(BadRequestException::class); $this->expectException(BadRequestException::class);

View file

@ -5,4 +5,4 @@ indent_style = space
insert_final_newline = true insert_final_newline = true
trim_trailing_whitespace = true trim_trailing_whitespace = true
end_of_line = lf end_of_line = lf
max_line_length = 80 max_line_length = 100

View file

@ -1,6 +1,5 @@
{ {
"$schema": "./node_modules/oxfmt/configuration_schema.json", "$schema": "./node_modules/oxfmt/configuration_schema.json",
"printWidth": 80,
"semi": false, "semi": false,
"singleQuote": true "singleQuote": true
} }

View file

@ -1,95 +0,0 @@
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')
})
})

View file

@ -1,138 +0,0 @@
<script setup lang="ts">
import { computed } from 'vue'
import {
ChevronLeft as ChevronLeftIcon,
ChevronRight as ChevronRightIcon,
} from 'lucide-vue-next'
import type { ChildElement } from '@/stores/elements'
type ElementRouteName = 'element' | 'admin-element'
type ElementNavigationPrefix = 'element' | 'admin'
interface Props {
currentElementId: number
dataCyPrefix: ElementNavigationPrefix
routeName: ElementRouteName
siblingElements: ChildElement[]
}
const props = defineProps<Props>()
const currentSiblingIndex = computed(() => {
return props.siblingElements.findIndex((siblingElement) => {
return siblingElement.id === props.currentElementId
})
})
const hasSiblingNavigation = computed(() => {
return props.siblingElements.length > 1 && currentSiblingIndex.value !== -1
})
const previousSibling = computed(() => {
return siblingElementAtOffset(-1)
})
const nextSibling = computed(() => {
return siblingElementAtOffset(1)
})
const navigationDataCy = computed(() => {
return `${props.dataCyPrefix}-sibling-navigation`
})
const previousDataCy = computed(() => {
return `${props.dataCyPrefix}-sibling-previous`
})
const nextDataCy = computed(() => {
return `${props.dataCyPrefix}-sibling-next`
})
function siblingElementAtOffset(offset: number): ChildElement | null {
if (!hasSiblingNavigation.value) {
return null
}
const siblingCount = props.siblingElements.length
const targetIndex =
(currentSiblingIndex.value + offset + siblingCount) % siblingCount
return props.siblingElements[targetIndex] ?? null
}
</script>
<template>
<nav
v-if="
hasSiblingNavigation && previousSibling !== null && nextSibling !== null
"
class="element-sibling-navigation"
:data-cy="navigationDataCy"
aria-label="Sibling elements"
>
<RouterLink
:to="{
name: routeName,
params: { id: previousSibling.id },
}"
class="element-sibling-navigation__link"
:data-cy="previousDataCy"
>
<ChevronLeftIcon :size="18" aria-hidden="true" />
<span>Previous</span>
</RouterLink>
<RouterLink
:to="{
name: routeName,
params: { id: nextSibling.id },
}"
class="element-sibling-navigation__link"
:data-cy="nextDataCy"
>
<span>Next</span>
<ChevronRightIcon :size="18" aria-hidden="true" />
</RouterLink>
</nav>
</template>
<style scoped>
.element-sibling-navigation {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 0.75rem;
margin: 1.35rem 0 0;
}
.element-sibling-navigation__link {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.4rem;
min-height: 2.5rem;
padding: 0.55rem 0.95rem;
border: 1px solid var(--color-border);
border-radius: 4px;
color: var(--color-text);
background: var(--color-white);
font-family: var(--font-sans);
font-size: 0.9rem;
font-weight: 600;
line-height: 1;
text-decoration: none;
transition:
border-color 180ms ease,
color 180ms ease;
}
.element-sibling-navigation__link:hover,
.element-sibling-navigation__link:focus-visible {
border-color: var(--color-slate);
color: var(--color-slate);
}
.element-sibling-navigation__link:focus-visible {
outline: 3px solid rgb(61 78 93 / 24%);
outline-offset: 3px;
}
</style>

View file

@ -73,13 +73,5 @@ export const useAuthStore = defineStore('auth', () => {
} }
} }
return { return { user, loginError, isSubmitting, isAuthenticated, login, fetchUser, logout }
user,
loginError,
isSubmitting,
isAuthenticated,
login,
fetchUser,
logout,
}
}) })

View file

@ -20,7 +20,6 @@ type ElementFileType = 'iconImage' | 'shortPdf' | 'longPdf'
interface ElementResponse { interface ElementResponse {
element: Element element: Element
childElements: ChildElement[] childElements: ChildElement[]
siblingElements: ChildElement[]
} }
export interface UpdateElementInput { export interface UpdateElementInput {
@ -59,7 +58,6 @@ const ELEMENT_UPDATE_URL = `${API_BASE_URL}/api/element/update`
export const useElementsStore = defineStore('elements', () => { export const useElementsStore = defineStore('elements', () => {
const element = ref<Element | null>(null) const element = ref<Element | null>(null)
const childElements = ref<ChildElement[]>([]) const childElements = ref<ChildElement[]>([])
const siblingElements = ref<ChildElement[]>([])
const isLoading = ref(false) const isLoading = ref(false)
const error = ref<string | null>(null) const error = ref<string | null>(null)
const isSaving = ref(false) const isSaving = ref(false)
@ -73,7 +71,6 @@ export const useElementsStore = defineStore('elements', () => {
async function fetchElement(elementId: string): Promise<void> { async function fetchElement(elementId: string): Promise<void> {
element.value = null element.value = null
childElements.value = [] childElements.value = []
siblingElements.value = []
error.value = null error.value = null
saveError.value = null saveError.value = null
childActionError.value = null childActionError.value = null
@ -97,10 +94,8 @@ export const useElementsStore = defineStore('elements', () => {
const data: ElementResponse = await response.json() const data: ElementResponse = await response.json()
element.value = data.element element.value = data.element
childElements.value = data.childElements childElements.value = data.childElements
siblingElements.value = data.siblingElements
} catch { } catch {
childElements.value = [] childElements.value = []
siblingElements.value = []
error.value = 'Network error - could not load element' error.value = 'Network error - could not load element'
} finally { } finally {
isLoading.value = false isLoading.value = false
@ -158,8 +153,8 @@ export const useElementsStore = defineStore('elements', () => {
childElementId.toString(), childElementId.toString(),
) )
const response = await fetch( const response = await fetch(
`${ELEMENTS_URL}/${encodedParentElementId}` + `${ELEMENTS_URL}/${encodedParentElementId}`
`/children/${encodedChildElementId}`, + `/children/${encodedChildElementId}`,
{ {
method: 'DELETE', method: 'DELETE',
credentials: 'include', credentials: 'include',
@ -393,7 +388,6 @@ export const useElementsStore = defineStore('elements', () => {
return { return {
element, element,
childElements, childElements,
siblingElements,
isLoading, isLoading,
error, error,
isSaving, isSaving,

View file

@ -73,8 +73,7 @@ const teachers: Teacher[] = [
}, },
{ {
name: 'Rabbi Efraim Greenblatt', name: 'Rabbi Efraim Greenblatt',
description: description: 'Halachic authority and loyal student of Rabbi Moshe Feinstein.',
'Halachic authority and loyal student of Rabbi Moshe Feinstein.',
}, },
{ {
name: 'The Sassover Rebbe, The Biala Rebbe, and Rabbi Mordechai Zukerman', name: 'The Sassover Rebbe, The Biala Rebbe, and Rabbi Mordechai Zukerman',
@ -231,8 +230,8 @@ function submitNewsletter(submitEvent: Event): void {
<p class="about-hero__eyebrow">Pilzno Institute</p> <p class="about-hero__eyebrow">Pilzno Institute</p>
<h1 class="about-hero__heading">About Rabbi Gerzi</h1> <h1 class="about-hero__heading">About Rabbi Gerzi</h1>
<p class="about-hero__lede"> <p class="about-hero__lede">
Healthy, Integrated and Balanced Torah living, rooted in mesorah Healthy, Integrated and Balanced Torah living, rooted in mesorah and made practical
and made practical for real people building real lives. for real people building real lives.
</p> </p>
</div> </div>
<img <img
@ -262,32 +261,25 @@ function submitNewsletter(submitEvent: Event): void {
</div> </div>
</section> </section>
<section <section class="about-section about-section--slate" data-cy="about-vision">
class="about-section about-section--slate"
data-cy="about-vision"
>
<div class="about-section__inner about-section__inner--narrow"> <div class="about-section__inner about-section__inner--narrow">
<p class="about-section__kicker">Vision</p> <p class="about-section__kicker">Vision</p>
<h2 class="about-section__heading">Rabbi Gerzi's Vision</h2> <h2 class="about-section__heading">Rabbi Gerzi's Vision</h2>
<p class="about-section__paragraph"> <p class="about-section__paragraph">
Rabbi Gerzi guides individuals from vagueness to clarity, from Rabbi Gerzi guides individuals from vagueness to clarity, from surviving to thriving,
surviving to thriving, from a galut to a geula mindset. Through from a galut to a geula mindset. Through profound teachings rooted in Torah wisdom,
profound teachings rooted in Torah wisdom, Rabbi Gerzi aims to Rabbi Gerzi aims to connect students with their divine purpose, discover balance, and
connect students with their divine purpose, discover balance, and
live a life of authentic joy and fulfillment. live a life of authentic joy and fulfillment.
</p> </p>
<p class="about-section__paragraph"> <p class="about-section__paragraph">
As spiritual growth can only be accomplished within a fertile social As spiritual growth can only be accomplished within a fertile social framework, Rabbi
framework, Rabbi Gerzi hosts regular weekly chaburot and various Gerzi hosts regular weekly chaburot and various events that create the conditions for
events that create the conditions for authentic life transformation. authentic life transformation.
</p> </p>
</div> </div>
</section> </section>
<section <section class="about-section about-section--cream" data-cy="about-mission">
class="about-section about-section--cream"
data-cy="about-mission"
>
<div class="about-section__inner"> <div class="about-section__inner">
<div class="about-section__header"> <div class="about-section__header">
<p class="about-section__kicker">Mission</p> <p class="about-section__kicker">Mission</p>
@ -295,10 +287,9 @@ function submitNewsletter(submitEvent: Event): void {
The Mission: To Provide a Clear Path to Wholesome Living The Mission: To Provide a Clear Path to Wholesome Living
</h2> </h2>
<p class="about-section__intro"> <p class="about-section__intro">
Rabbi Gerzi provides a structured path to wisdom, personal growth, Rabbi Gerzi provides a structured path to wisdom, personal growth, and transformation.
and transformation. His educational system is designed to guide His educational system is designed to guide individuals through essential life areas,
individuals through essential life areas, creating not just creating not just intellectual understanding but genuine change.
intellectual understanding but genuine change.
</p> </p>
</div> </div>
@ -327,35 +318,27 @@ function submitNewsletter(submitEvent: Event): void {
</div> </div>
<div class="about-section__body"> <div class="about-section__body">
<p class="about-section__paragraph"> <p class="about-section__paragraph">
Rabbi Gerzi's teachings are deeply influenced by his mentor, Rabbi Rabbi Gerzi's teachings are deeply influenced by his mentor, Rabbi Yosef Singer, the
Yosef Singer, the Pilzno Rav. Under Rabbi Singer's guidance, Rabbi Pilzno Rav. Under Rabbi Singer's guidance, Rabbi Gerzi absorbed the teachings of the
Gerzi absorbed the teachings of the Baal Shem Tov, Chassidut, Baal Shem Tov, Chassidut, Kabbalah, and Halacha.
Kabbalah, and Halacha.
</p> </p>
<p class="about-section__paragraph"> <p class="about-section__paragraph">
In 2005, the Pilzno Rav instructed Rabbi Gerzi to continue his In 2005, the Pilzno Rav instructed Rabbi Gerzi to continue his legacy by building a
legacy by building a community in Eretz Yisrael dedicated to this community in Eretz Yisrael dedicated to this unique form of Healthy, Integrated and
unique form of Healthy, Integrated and Balanced Torah living. Balanced Torah living.
</p> </p>
</div> </div>
</div> </div>
</section> </section>
<section <section class="about-section about-section--cream" data-cy="about-teachers">
class="about-section about-section--cream"
data-cy="about-teachers"
>
<div class="about-section__inner"> <div class="about-section__inner">
<div class="about-section__header"> <div class="about-section__header">
<p class="about-section__kicker">Teachers</p> <p class="about-section__kicker">Teachers</p>
<h2 class="about-section__heading">Rabbi Gerzi's Teachers</h2> <h2 class="about-section__heading">Rabbi Gerzi's Teachers</h2>
</div> </div>
<ol class="teacher-list"> <ol class="teacher-list">
<li <li v-for="teacher in teachers" :key="teacher.name" class="teacher-list__item">
v-for="teacher in teachers"
:key="teacher.name"
class="teacher-list__item"
>
<span class="teacher-list__name">{{ teacher.name }}</span> <span class="teacher-list__name">{{ teacher.name }}</span>
<span class="teacher-list__description"> <span class="teacher-list__description">
{{ teacher.description }} {{ teacher.description }}
@ -414,8 +397,7 @@ function submitNewsletter(submitEvent: Event): void {
<div class="get-involved__copy"> <div class="get-involved__copy">
<h2 class="get-involved__heading">Get Involved</h2> <h2 class="get-involved__heading">Get Involved</h2>
<p class="get-involved__subtext"> <p class="get-involved__subtext">
Subscribe for updates as we release new content, chaburot and Subscribe for updates as we release new content, chaburot and events.
events.
</p> </p>
</div> </div>
<form class="get-involved__form" @submit="submitNewsletter"> <form class="get-involved__form" @submit="submitNewsletter">

View file

@ -2,7 +2,6 @@
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { computed, reactive, ref, watch } from 'vue' import { computed, reactive, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import ElementSiblingNavigation from '@/components/ElementSiblingNavigation.vue'
import RichTextEditor from '@/components/RichTextEditor.vue' import RichTextEditor from '@/components/RichTextEditor.vue'
import SiteHeader from '@/components/SiteHeader.vue' import SiteHeader from '@/components/SiteHeader.vue'
import { useElementsStore } from '@/stores/elements' import { useElementsStore } from '@/stores/elements'
@ -24,7 +23,6 @@ const elementsStore = useElementsStore()
const { const {
element, element,
childElements, childElements,
siblingElements,
isLoading, isLoading,
error, error,
isSaving, isSaving,
@ -619,13 +617,6 @@ function resetInput(changeEvent: Event): void {
{{ statusMessage }} {{ statusMessage }}
</p> </p>
</div> </div>
<ElementSiblingNavigation
:current-element-id="element.id"
:sibling-elements="siblingElements"
data-cy-prefix="admin"
route-name="admin-element"
/>
</form> </form>
</main> </main>
</div> </div>

View file

@ -40,20 +40,15 @@ function submitNewsletter(submitEvent: Event): void {
<p class="contact-hero__eyebrow">Contact</p> <p class="contact-hero__eyebrow">Contact</p>
<h1 class="contact-hero__heading">Contact</h1> <h1 class="contact-hero__heading">Contact</h1>
<p class="contact-hero__body"> <p class="contact-hero__body">
Rabbi Gerzi receives numerous messages each week. It may take some Rabbi Gerzi receives numerous messages each week. It may take some time to get back to
time to get back to you, but we will do our best to respond in a you, but we will do our best to respond in a timely manner.
timely manner.
</p> </p>
</div> </div>
</section> </section>
<section class="contact-main"> <section class="contact-main">
<div class="contact-main__inner"> <div class="contact-main__inner">
<form <form class="contact-form" data-cy="contact-form" @submit="submitContact">
class="contact-form"
data-cy="contact-form"
@submit="submitContact"
>
<h2 class="contact-form__heading">Send Rabbi Gerzi a Message</h2> <h2 class="contact-form__heading">Send Rabbi Gerzi a Message</h2>
<div class="contact-form__grid"> <div class="contact-form__grid">
<label class="contact-form__field"> <label class="contact-form__field">
@ -126,9 +121,7 @@ function submitNewsletter(submitEvent: Event): void {
</form> </form>
<aside class="contact-social" data-cy="contact-social"> <aside class="contact-social" data-cy="contact-social">
<h2 class="contact-social__heading"> <h2 class="contact-social__heading">Follow Rabbi Gerzi on Social Media</h2>
Follow Rabbi Gerzi on Social Media
</h2>
<div class="contact-social__links"> <div class="contact-social__links">
<a <a
v-for="socialLink in socialLinks" v-for="socialLink in socialLinks"
@ -150,8 +143,7 @@ function submitNewsletter(submitEvent: Event): void {
<div class="get-involved__copy"> <div class="get-involved__copy">
<h2 class="get-involved__heading">Get Involved</h2> <h2 class="get-involved__heading">Get Involved</h2>
<p class="get-involved__subtext"> <p class="get-involved__subtext">
Subscribe for updates as we release new content, chaburot and Subscribe for updates as we release new content, chaburot and events.
events.
</p> </p>
</div> </div>
<form class="get-involved__form" @submit="submitNewsletter"> <form class="get-involved__form" @submit="submitNewsletter">

View file

@ -2,7 +2,6 @@
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { computed, watch } from 'vue' import { computed, watch } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import ElementSiblingNavigation from '@/components/ElementSiblingNavigation.vue'
import SiteHeader from '@/components/SiteHeader.vue' import SiteHeader from '@/components/SiteHeader.vue'
import { useElementsStore } from '@/stores/elements' import { useElementsStore } from '@/stores/elements'
@ -10,8 +9,7 @@ type TimestampPart = string | undefined
const route = useRoute() const route = useRoute()
const elementsStore = useElementsStore() const elementsStore = useElementsStore()
const { element, childElements, siblingElements, isLoading, error } = const { element, childElements, isLoading, error } = storeToRefs(elementsStore)
storeToRefs(elementsStore)
const youtubeIframeAllow = [ const youtubeIframeAllow = [
'accelerometer', 'accelerometer',
@ -299,13 +297,6 @@ function isShortYoutubeHost(hostname: string): boolean {
</li> </li>
</ul> </ul>
</nav> </nav>
<ElementSiblingNavigation
:current-element-id="element.id"
:sibling-elements="siblingElements"
data-cy-prefix="element"
route-name="element"
/>
</section> </section>
</main> </main>
</div> </div>

View file

@ -42,9 +42,7 @@ const testimonials: Testimonial[] = [
}, },
{ {
name: 'Anonymous Student', name: 'Anonymous Student',
quote: quote: 'My understanding of Torah has been profoundly improved through ' + 'his teachings.',
'My understanding of Torah has been profoundly improved through ' +
'his teachings.',
}, },
{ {
name: 'Placeholder Student 1', name: 'Placeholder Student 1',
@ -269,8 +267,7 @@ function projectCardStyle(project: Project): string {
<div class="hero__text"> <div class="hero__text">
<h1 class="hero__headline"> <h1 class="hero__headline">
Let&rsquo;s upgrade our lives to a Let&rsquo;s upgrade our lives to a
<em>healthy</em>, <em>integrated</em>, and <em>balanced</em> Torah <em>healthy</em>, <em>integrated</em>, and <em>balanced</em> Torah existence.
existence.
</h1> </h1>
<p class="hero__attribution">Rabbi Yehoshua Gerzi</p> <p class="hero__attribution">Rabbi Yehoshua Gerzi</p>
</div> </div>
@ -300,29 +297,23 @@ function projectCardStyle(project: Project): string {
<path d="M10 65 L40 25 L55 45 L70 15 L90 45 L110 65" /> <path d="M10 65 L40 25 L55 45 L70 15 L90 45 L110 65" />
<path d="M30 65 C45 55, 60 60, 75 50 S100 55, 110 65" /> <path d="M30 65 C45 55, 60 60, 75 50 S100 55, 110 65" />
</svg> </svg>
<h2 class="discover-path__heading"> <h2 class="discover-path__heading">Discover a Path to a Holistically Engaged Life</h2>
Discover a Path to a Holistically Engaged Life
</h2>
<p class="discover-path__paragraph"> <p class="discover-path__paragraph">
Rabbi Yehoshua Gerzi has dedicated his life to empowering individuals Rabbi Yehoshua Gerzi has dedicated his life to empowering individuals to achieve a state
to achieve a state of &ldquo;Healthy, Integrated and Balanced Torah of &ldquo;Healthy, Integrated and Balanced Torah Living.&rdquo; His teachings weave
Living.&rdquo; His teachings weave together timeless Torah wisdom, together timeless Torah wisdom, mystical insights and practical guidance, making ancient
mystical insights and practical guidance, making ancient wisdom wisdom accessible and transformative for modern lives.
accessible and transformative for modern lives.
</p> </p>
<p class="discover-path__paragraph"> <p class="discover-path__paragraph">
Rabbi Gerzi is based in Ramat Beit Shemesh, where he serves as Rav of Rabbi Gerzi is based in Ramat Beit Shemesh, where he serves as Rav of Kehillas Beis David
Kehillas Beis David and Director of the Pilzno Institute of Higher and Director of the Pilzno Institute of Higher Learning.
Learning.
</p> </p>
</div> </div>
</section> </section>
<section class="core-teachings" data-cy="core-teachings"> <section class="core-teachings" data-cy="core-teachings">
<div class="core-teachings__inner"> <div class="core-teachings__inner">
<h2 class="core-teachings__heading"> <h2 class="core-teachings__heading">Baderech HaAvodah (Core Teachings)</h2>
Baderech HaAvodah (Core Teachings)
</h2>
<div class="core-teachings__grid"> <div class="core-teachings__grid">
<div <div
v-for="teaching in coreTeachings" v-for="teaching in coreTeachings"
@ -332,9 +323,7 @@ function projectCardStyle(project: Project): string {
<span class="core-teachings__glyph" aria-hidden="true"> <span class="core-teachings__glyph" aria-hidden="true">
{{ teaching.glyph }} {{ teaching.glyph }}
</span> </span>
<p class="core-teachings__label"> <p class="core-teachings__label">{{ teaching.number }}) {{ teaching.title }}</p>
{{ teaching.number }}) {{ teaching.title }}
</p>
</div> </div>
</div> </div>
</div> </div>
@ -359,18 +348,13 @@ function projectCardStyle(project: Project): string {
</svg> </svg>
<h2 class="unique-voice__heading">A Unique Voice for the Generation</h2> <h2 class="unique-voice__heading">A Unique Voice for the Generation</h2>
<p class="unique-voice__paragraph"> <p class="unique-voice__paragraph">
Rabbi Gerzi provides a wholly unique and much needed perspective to Rabbi Gerzi provides a wholly unique and much needed perspective to his students. Carrying
his students. Carrying decades of experience guided by a handful of decades of experience guided by a handful of the generations most pre-eminent Rabbanim, he
the generations most pre-eminent Rabbanim, he teaches a message of teaches a message of practically engaged positivity. His Torah is one of unity, bridging
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
hashkafic boundaries and engaging a diverse cadre of Jews who are open meaningful and experiential Judaism.
minded to embracing meaningful and experiential Judaism.
</p> </p>
<button <button class="unique-voice__cta-button" type="button" @click="goToAbout">
class="unique-voice__cta-button"
type="button"
@click="goToAbout"
>
Learn about Rabbi Gerzi&rsquo;s Approach Learn about Rabbi Gerzi&rsquo;s Approach
</button> </button>
</div> </div>
@ -389,9 +373,7 @@ function projectCardStyle(project: Project): string {
:aria-hidden="testimonialPosition(index) !== 'active'" :aria-hidden="testimonialPosition(index) !== 'active'"
> >
<h3 class="testimonials__name">{{ testimonial.name }}</h3> <h3 class="testimonials__name">{{ testimonial.name }}</h3>
<p class="testimonials__quote"> <p class="testimonials__quote">&ldquo;{{ testimonial.quote }}&rdquo;</p>
&ldquo;{{ testimonial.quote }}&rdquo;
</p>
</div> </div>
</div> </div>
<div class="testimonials__controls"> <div class="testimonials__controls">
@ -419,9 +401,7 @@ function projectCardStyle(project: Project): string {
<section class="sponsors" data-cy="sponsors"> <section class="sponsors" data-cy="sponsors">
<div class="sponsors__inner"> <div class="sponsors__inner">
<h2 class="sponsors__heading"> <h2 class="sponsors__heading">Organizations Rabbi Gerzi has Worked With</h2>
Organizations Rabbi Gerzi has Worked With
</h2>
<div class="sponsors__carousel" data-cy="sponsor-carousel"> <div class="sponsors__carousel" data-cy="sponsor-carousel">
<div class="sponsors__track" data-cy="sponsor-track"> <div class="sponsors__track" data-cy="sponsor-track">
<div <div
@ -467,15 +447,9 @@ function projectCardStyle(project: Project): string {
<div class="journey__inner"> <div class="journey__inner">
<h2 class="journey__heading">Start Your Journey</h2> <h2 class="journey__heading">Start Your Journey</h2>
<div class="journey__cards"> <div class="journey__cards">
<a <a class="journey__card journey__card--olive" href="/media" data-cy="journey-media">
class="journey__card journey__card--olive"
href="/media"
data-cy="journey-media"
>
<h3 class="journey__card-title">Access<br />Torah Media</h3> <h3 class="journey__card-title">Access<br />Torah Media</h3>
<p class="journey__card-body"> <p class="journey__card-body">Explore a wealth of video, audio and written content.</p>
Explore a wealth of video, audio and written content.
</p>
</a> </a>
<a <a
class="journey__card journey__card--slate" class="journey__card journey__card--slate"
@ -496,8 +470,7 @@ function projectCardStyle(project: Project): string {
<div class="get-involved__copy"> <div class="get-involved__copy">
<h2 class="get-involved__heading">Get Involved</h2> <h2 class="get-involved__heading">Get Involved</h2>
<p class="get-involved__subtext"> <p class="get-involved__subtext">
Subscribe for updates as we release new content, chaburot and Subscribe for updates as we release new content, chaburot and events.
events.
</p> </p>
</div> </div>
<form class="get-involved__form" @submit="submitNewsletter"> <form class="get-involved__form" @submit="submitNewsletter">

View file

@ -45,11 +45,7 @@ async function handleSubmit(): Promise<void> {
/> />
</label> </label>
<p <p v-if="authStore.loginError" data-cy="login-error" class="login-card__error">
v-if="authStore.loginError"
data-cy="login-error"
class="login-card__error"
>
{{ authStore.loginError }} {{ authStore.loginError }}
</p> </p>