From 1492273fff51fb9f5062cc28f932ca0ecd499919 Mon Sep 17 00:00:00 2001
From: Sophia Kuhlmann <sophiakuhl27@gmail.com>
Date: Thu, 27 Mar 2025 13:24:07 +0100
Subject: [PATCH] HOLI-11279 layout improvements to events screen

---
 apps/web/pages/events/index.tsx               |  10 +-
 holi-apps/events/components/EventCarousel.tsx |   4 +-
 .../events/components/EventsFilterForm.tsx    |  10 +-
 holi-apps/events/screens/EventList.tsx        | 158 ++++++++----------
 holi-bricks/components/card/CompactCard.tsx   |   2 +-
 holi-bricks/components/image/Image.tsx        |  60 ++++---
 holi-bricks/components/image/Image.web.tsx    |  55 ++++--
 holi-bricks/components/image/type.ts          |   1 +
 8 files changed, 162 insertions(+), 138 deletions(-)

diff --git a/apps/web/pages/events/index.tsx b/apps/web/pages/events/index.tsx
index 8653312795..483e3dcca2 100644
--- a/apps/web/pages/events/index.tsx
+++ b/apps/web/pages/events/index.tsx
@@ -1,7 +1,11 @@
 import { EventList } from '@holi-apps/events/screens/EventList'
-import type { NextPage } from 'next'
-import React from 'react'
+import React, { type ReactElement } from 'react'
 
-const EventsPage: NextPage = () => <EventList />
+import { AppLayout } from '@holi/web/layout/NavigationLayoutVolunteeringApp'
+import type { NextPageWithLayout } from '@holi/web/pages/_app'
+
+const EventsPage: NextPageWithLayout = () => <EventList />
+
+EventsPage.getLayout = (page: ReactElement) => <AppLayout>{page}</AppLayout>
 
 export default EventsPage
diff --git a/holi-apps/events/components/EventCarousel.tsx b/holi-apps/events/components/EventCarousel.tsx
index c62cfe8f72..ec05c4f7f4 100644
--- a/holi-apps/events/components/EventCarousel.tsx
+++ b/holi-apps/events/components/EventCarousel.tsx
@@ -1,6 +1,6 @@
 import React from 'react'
 import RecoSlider from '@holi/core/components/RecoSlider'
-import { CheckboxFilled } from '@holi/icons/src/generated'
+import { CalendarEventFilled } from '@holi/icons/src/generated'
 import { useTranslation } from 'react-i18next'
 import EventCard from '@holi-apps/events/components/EventCard'
 import { Stack } from 'holi-bricks/components/stack'
@@ -37,7 +37,7 @@ const EventCarousel = ({ cardWidth }: EventCarouselProps) => {
       }}
       data={events}
       loading={user.isLoading}
-      icon={CheckboxFilled}
+      icon={CalendarEventFilled}
       heading={t('act.events.title')}
       subline={t('act.events.subline')}
       labelSeeMore={t('act.events.showAll')}
diff --git a/holi-apps/events/components/EventsFilterForm.tsx b/holi-apps/events/components/EventsFilterForm.tsx
index 5695aac3f3..b7ecc2d2da 100644
--- a/holi-apps/events/components/EventsFilterForm.tsx
+++ b/holi-apps/events/components/EventsFilterForm.tsx
@@ -47,7 +47,15 @@ const EventsFilterForm = ({ onSubmit, filters }: EventFilterFormProps) => {
       <View style={styles.modalContent}>
         <Stack direction="row" alignItems="center" justifyContent="space-between" gap="3xs">
           <Text size="3xl">{t('act.events.filter.heading')}</Text>
-          <Button label={t('global.reset')} inline onPress={resetForm} variant="secondary" size="sm" icon={Close} />
+          <Button
+            disabled={localOnly}
+            label={t('global.reset')}
+            inline
+            onPress={resetForm}
+            variant="secondary"
+            size="sm"
+            icon={Close}
+          />
         </Stack>
 
         <Stack direction="row" alignItems="center" justifyContent="space-between">
diff --git a/holi-apps/events/screens/EventList.tsx b/holi-apps/events/screens/EventList.tsx
index 50dbf7d924..ca9505b967 100644
--- a/holi-apps/events/screens/EventList.tsx
+++ b/holi-apps/events/screens/EventList.tsx
@@ -9,11 +9,8 @@ import HoliGridWrapper from '@holi/ui/components/atoms/HoliGridWrapper'
 import { Image } from 'holi-bricks/components/image'
 import HoliModal, { useModalState } from '@holi/ui/components/molecules/HoliModal'
 
-import { createStyleSheet } from 'holi-bricks/utils'
-import { useStyles } from 'holi-bricks/hooks'
 import { Text } from 'holi-bricks/components/text'
 import HeaderBackButton from '@holi/core/navigation/components/HeaderBackButton'
-import HoliContainer from '@holi/ui/components/atoms/HoliContainer'
 import HoliButton from '@holi/ui/components/molecules/HoliButton'
 import EventsFilterForm, { type EventFilters } from '@holi-apps/events/components/EventsFilterForm'
 import EventCard from '@holi-apps/events/components/EventCard'
@@ -32,9 +29,9 @@ const PAGE_SIZE_EXTERNAL_DATA = 10
 
 export const EventList = () => {
   const { t } = useTranslation()
-  const { styles } = useStyles(stylesheet)
 
   const [fetchLocalOnlyEvents, setFetchLocalOnlyEvents] = useState(false)
+  const [isFiltering, setIsFiltering] = useState(false)
   const { events, canLoadMore, fetchMoreEvents } = useEventListData(PAGE_SIZE_EXTERNAL_DATA, fetchLocalOnlyEvents)
 
   const partner = EVENTS_SOURCE_MAPPING
@@ -47,6 +44,7 @@ export const EventList = () => {
   const handleFilterChange = useCallback(
     async (filters: EventFilters) => {
       setFetchLocalOnlyEvents(filters.localOnly)
+      setIsFiltering(filters.localOnly)
       closeFilterModal()
     },
     [closeFilterModal]
@@ -59,6 +57,7 @@ export const EventList = () => {
       onPress={showFilterModal}
       testID="btn-to-volunteering-filter-modal"
       trackingEvent={TrackingEvent.Events.filter}
+      showBell={isFiltering}
     />
   )
 
@@ -78,100 +77,75 @@ export const EventList = () => {
         headerTitleAlign: 'center',
       }}
     >
-      <HoliContainer>
-        <Stack padding={{ top: 'md' }}>
-          {events && events.length > 0 && (
-            <Stack gap="xs">
-              <Text headingLevel="2" size="3xl">
-                {t('act.events.title')}
-              </Text>
+      <Stack padding={{ top: 'md' }}>
+        {events && events.length > 0 && (
+          <Stack gap="xs">
+            <Text headingLevel="2" size="3xl">
+              {t('act.events.title')}
+            </Text>
+            <HoliButton transparent onPress={showPartnerModal} label={t('act.events.partnerInfoRowText')}>
               <Stack direction="row" alignItems="center" gap="3xs">
-                <Stack borderRadius="pill">
-                  <View style={styles.imageContainer}>
-                    <Image
-                      source={require('@holi/ui/assets/img/events/gemeinschaftswerk-nachhaltigkeit-logo.png')}
-                      width={33}
-                      height={33}
-                      alt="Logo Gemeinschaftswerk Nachhaltigkeit"
-                    />
-                  </View>
-                </Stack>
-                <HoliButton transparent onPress={showPartnerModal} label={t('act.events.partnerInfoRowText')}>
-                  <View style={{ flex: 3 }}>
-                    <Text size="lg">{t('act.events.partnerInfoRowText')}</Text>
-                  </View>
-                </HoliButton>
+                <Image
+                  source={require('@holi/ui/assets/img/events/gemeinschaftswerk-nachhaltigkeit-logo.png')}
+                  width={33}
+                  height={33}
+                  alt="Logo Gemeinschaftswerk Nachhaltigkeit"
+                />
+                <View style={{ flex: 3 }}>
+                  <Text size="lg">{t('act.events.partnerInfoRowText')}</Text>
+                </View>
               </Stack>
+            </HoliButton>
 
-              <Text size="lg" color="support">
-                {events.length} {t('act.events.opportunities')}
-              </Text>
+            <Text size="lg" color="support">
+              {events.length} {t('act.events.opportunities')}
+            </Text>
 
-              {/* info modal */}
-              <InfoModal
-                headline={t('volunteering:listing.partnerModal.headline')}
-                items={partner}
-                shown={partnerModalVisible}
-                onDismiss={closePartnerModal}
-                i18nPrefix={'act.events.listing.partnerModal'}
-              />
+            {/* info modal */}
+            <InfoModal
+              headline={t('volunteering:listing.partnerModal.headline')}
+              items={partner}
+              shown={partnerModalVisible}
+              onDismiss={closePartnerModal}
+              i18nPrefix={'act.events.listing.partnerModal'}
+            />
 
-              {/* filter modal */}
-              <HoliModal label={'Filter Events'} shown={filterModalVisible} onDismiss={closeFilterModal} autoHeight>
-                <EventsFilterForm
-                  filters={{
-                    localOnly: fetchLocalOnlyEvents,
-                  }}
-                  onSubmit={handleFilterChange}
-                />
-              </HoliModal>
-            </Stack>
-          )}
-          <HoliBox padding={[24, 0, 24, 0]}>
-            {/* <View testID={isFiltering ? 'volunteering-list-filtered' : 'volunteering-list-unfiltered'}> */}
-            <HoliGridWrapper>
-              {Array.isArray(events) &&
-                events.map((event) => {
-                  return (
-                    <HoliGridCell key={event.id}>
-                      <EventCard event={event} />
-                    </HoliGridCell>
-                  )
-                })}
-            </HoliGridWrapper>
-            {/* </View> */}
-          </HoliBox>
-          {canLoadMore && (
-            <Stack padding={{ bottom: 'md' }}>
-              <ButtonTracked
-                label={t('global.seeMore')}
-                loadingLabel={t('global.loading')}
-                inline
-                onPress={fetchMoreEvents}
-                testID="btn-fetch-more"
-                trackingEvent={TrackingEvent.Events.loadMore}
+            {/* filter modal */}
+            <HoliModal label={'Filter Events'} shown={filterModalVisible} onDismiss={closeFilterModal} autoHeight>
+              <EventsFilterForm
+                filters={{
+                  localOnly: fetchLocalOnlyEvents,
+                }}
+                onSubmit={handleFilterChange}
               />
-            </Stack>
-          )}
-        </Stack>
-      </HoliContainer>
+            </HoliModal>
+          </Stack>
+        )}
+        <HoliBox padding={[24, 0, 24, 0]}>
+          <HoliGridWrapper gap={8}>
+            {Array.isArray(events) &&
+              events.map((event) => {
+                return (
+                  <HoliGridCell key={event.id}>
+                    <EventCard event={event} />
+                  </HoliGridCell>
+                )
+              })}
+          </HoliGridWrapper>
+        </HoliBox>
+        {canLoadMore && (
+          <Stack padding={{ bottom: 'md' }}>
+            <ButtonTracked
+              label={t('global.seeMore')}
+              loadingLabel={t('global.loading')}
+              inline
+              onPress={fetchMoreEvents}
+              testID="btn-fetch-more"
+              trackingEvent={TrackingEvent.Events.loadMore}
+            />
+          </Stack>
+        )}
+      </Stack>
     </Screen>
   )
 }
-
-const stylesheet = createStyleSheet((theme) => ({
-  defaultLayout: {
-    backgroundColor: theme.colors.bg.page,
-    paddingBottom: 0,
-  },
-  imageContainer: {
-    borderColor: theme.colors.border.default,
-    overflow: 'hidden',
-    display: 'flex',
-    justifyContent: 'center',
-    justifySelf: 'center',
-    alignContent: 'center',
-    alignSelf: 'center',
-    textAlign: 'center',
-  },
-}))
diff --git a/holi-bricks/components/card/CompactCard.tsx b/holi-bricks/components/card/CompactCard.tsx
index 8d64b4db9f..937ed178bb 100644
--- a/holi-bricks/components/card/CompactCard.tsx
+++ b/holi-bricks/components/card/CompactCard.tsx
@@ -55,7 +55,6 @@ export const CompactCard = ({
     require('@holi/ui/assets/img/events/thumbnail-yellow-full.svg'),
   ]
   const randomPlaceholderImage = useRef(placeholderImages[Math.floor(Math.random() * placeholderImages.length)]).current
-
   return (
     <View style={styles.cardContainer(maxWidth)}>
       <Stack borderWidth={1} borderColor="support" borderStyle="solid" borderRadius="md">
@@ -70,6 +69,7 @@ export const CompactCard = ({
                 width={90}
                 alt={t('act.events.thumbnail.label')}
                 onError={handleImageError}
+                whiteOverlay
               />
             </View>
 
diff --git a/holi-bricks/components/image/Image.tsx b/holi-bricks/components/image/Image.tsx
index 1e912184d2..87f34356ac 100644
--- a/holi-bricks/components/image/Image.tsx
+++ b/holi-bricks/components/image/Image.tsx
@@ -1,11 +1,13 @@
 import type React from 'react'
 import { useState } from 'react'
-import { Image as RNImage } from 'react-native'
+import { Image as RNImage, View } from 'react-native'
 import { Image as ExpoImage } from 'expo-image'
 //import { WithLocalSvg } from 'react-native-svg/css'
 import { accessibilityProps } from 'holi-bricks/accessibility'
 import type { ImageProps } from 'holi-bricks/components/image/type'
 import { setScalingParams } from 'holi-bricks/components/image/helpers'
+import { createStyleSheet } from 'holi-bricks/utils'
+import { useStyles } from 'holi-bricks/hooks'
 
 const isLocalSvg = (source: number) => {
   try {
@@ -32,12 +34,14 @@ export const Image: React.FC<ImageProps> = ({
   fallbackSource,
   blurhash,
   alt,
+  whiteOverlay: overlay,
   ...otherProps
 }) => {
   const [hasError, setHasError] = useState(false)
   const transformedSource = setScalingParams(source, width, height)
   const isRemote = isRemoteSource(source)
   const localSvg = typeof source === 'number' && isLocalSvg(source)
+  const { styles } = useStyles(stylesheet)
 
   if (!source) return null
 
@@ -59,25 +63,39 @@ export const Image: React.FC<ImageProps> = ({
   }
 
   return (
-    <ExpoImage
-      source={hasError && fallbackSource ? fallbackSource : transformedSource}
-      alt={alt}
-      role="img"
-      accessibilityLabel={alt}
-      accessibilityRole={otherProps?.['aria-hidden'] === true ? 'none' : 'image'}
-      accessible={otherProps?.['aria-hidden'] === true}
-      placeholder={isRemote ? blurhash : undefined}
-      style={[{ width, height }]}
-      contentFit={resizeMode}
-      onError={() => {
-        setHasError(true)
-        // eslint-disable-next-line no-console
-        console.warn(`[Image] Failed to load image: ${source}`)
-        if (onError) onError()
-      }}
-      aria-label={alt}
-      onLoadEnd={onLoadEnd}
-      {...accessibilityProps(otherProps)}
-    />
+    <>
+      <ExpoImage
+        source={hasError && fallbackSource ? fallbackSource : transformedSource}
+        alt={alt}
+        role="img"
+        accessibilityLabel={alt}
+        accessibilityRole={otherProps?.['aria-hidden'] === true ? 'none' : 'image'}
+        accessible={otherProps?.['aria-hidden'] === true}
+        placeholder={isRemote ? blurhash : undefined}
+        style={[{ width, height }]}
+        contentFit={resizeMode}
+        onError={() => {
+          setHasError(true)
+          // eslint-disable-next-line no-console
+          console.warn(`[Image] Failed to load image: ${source}`)
+          if (onError) onError()
+        }}
+        aria-label={alt}
+        onLoadEnd={onLoadEnd}
+        {...accessibilityProps(otherProps)}
+      />
+      {overlay && <View style={styles.overlay} />}
+    </>
   )
 }
+
+const stylesheet = createStyleSheet({
+  overlay: {
+    position: 'absolute',
+    top: 0,
+    left: 0,
+    width: '100%',
+    height: '100%',
+    backgroundColor: 'rgba(255, 255, 255, 0.2)',
+  },
+})
diff --git a/holi-bricks/components/image/Image.web.tsx b/holi-bricks/components/image/Image.web.tsx
index fc0b18b1e8..9be9ad7440 100644
--- a/holi-bricks/components/image/Image.web.tsx
+++ b/holi-bricks/components/image/Image.web.tsx
@@ -4,6 +4,9 @@ import { useState } from 'react'
 import NextImage from 'next/image'
 import { accessibilityProps } from 'holi-bricks/accessibility'
 import { setScalingParams } from 'holi-bricks/components/image/helpers'
+import { createStyleSheet } from 'holi-bricks/utils'
+import { useStyles } from 'holi-bricks/hooks'
+import { View } from 'react-native'
 
 export const isRemoteSource = (source: string): source is string => {
   return /^(https?:)?\/\//i.test(source)
@@ -19,30 +22,46 @@ export const Image: React.FC<ImageProps> = ({
   onLoadEnd,
   fallbackSource,
   blurhash,
+  whiteOverlay: overlay,
   ...otherProps
 }) => {
   const [hasError, setHasError] = useState(false)
   const isRemote = isRemoteSource(source as string)
   const transformedSource = (isRemote ? setScalingParams(source as string, width, height) : source) as string
+  const { styles } = useStyles(stylesheet)
 
   return (
-    <NextImage
-      src={hasError && fallbackSource ? (fallbackSource as string) : transformedSource}
-      alt={alt}
-      role="img"
-      aria-label={alt}
-      blurDataURL={isRemote ? blurhash : undefined}
-      width={width}
-      height={height}
-      style={{ objectFit: resizeMode }}
-      onError={() => {
-        setHasError(true)
-        // eslint-disable-next-line no-console
-        console.warn(`[Image] Failed to load image: ${transformedSource}`)
-        if (onError) onError()
-      }}
-      onLoad={onLoadEnd}
-      {...accessibilityProps(otherProps)}
-    />
+    <>
+      <NextImage
+        src={hasError && fallbackSource ? (fallbackSource as string) : transformedSource}
+        alt={alt}
+        role="img"
+        aria-label={alt}
+        blurDataURL={isRemote ? blurhash : undefined}
+        width={width}
+        height={height}
+        style={{ objectFit: resizeMode }}
+        onError={() => {
+          setHasError(true)
+          // eslint-disable-next-line no-console
+          console.warn(`[Image] Failed to load image: ${transformedSource}`)
+          if (onError) onError()
+        }}
+        onLoad={onLoadEnd}
+        {...accessibilityProps(otherProps)}
+      />
+      {overlay && <View style={styles.overlay} />}
+    </>
   )
 }
+
+const stylesheet = createStyleSheet({
+  overlay: {
+    position: 'absolute',
+    top: 0,
+    left: 0,
+    width: '100%',
+    height: '100%',
+    backgroundColor: 'rgba(255, 255, 255, 0.2)',
+  },
+})
diff --git a/holi-bricks/components/image/type.ts b/holi-bricks/components/image/type.ts
index 322c58b0c6..861a319696 100644
--- a/holi-bricks/components/image/type.ts
+++ b/holi-bricks/components/image/type.ts
@@ -13,4 +13,5 @@ export interface ImageProps extends AccessibilityProps {
   fallbackSource?: string | number
   blurhash?: string
   alt: string
+  whiteOverlay?: boolean
 }
-- 
GitLab