Skip to content

Commit 3fe038d

Browse files
hhkaosclaude
andcommitted
feat(eventos): agrega menús desplegables de suscripción para RSS y OTE
Convierte los badges de RSS y OTE en dropdowns como el de ICS, con enlaces a Feedly y feed:// para RSS, y al lector y vista previa de OTE, manteniendo el XML/JSON crudo como última opción. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 146c208 commit 3fe038d

2 files changed

Lines changed: 130 additions & 37 deletions

File tree

src/contexts/LanguageContext.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,12 @@ const translations = {
8888
'events.subscribeGoogle': 'Subscribe with Google Calendar',
8989
'events.subscribeOutlook': 'Subscribe with Outlook',
9090
'events.downloadIcs': 'Open/download ICS',
91+
'events.subscribeFeedly': 'Subscribe with Feedly',
92+
'events.subscribeFeedReader': 'Subscribe with feed reader (NetNewsWire, Reeder…)',
93+
'events.downloadRss': 'Open/download RSS',
94+
'events.subscribeOteReader': 'Subscribe with OTE Reader',
95+
'events.previewOte': 'Preview in OTE Tools',
96+
'events.downloadJson': 'Open/download JSON',
9197

9298
// Footer
9399
'footer.description': 'We are a collective that brings together people who energize Spanish-speaking tech communities with the aim of being a reference place where collaboration and exchange of experiences are facilitated.',
@@ -165,6 +171,12 @@ const translations = {
165171
'events.subscribeGoogle': 'Suscribirse con Google Calendar',
166172
'events.subscribeOutlook': 'Suscribirse con Outlook',
167173
'events.downloadIcs': 'Abrir/descargar ICS',
174+
'events.subscribeFeedly': 'Suscribirse con Feedly',
175+
'events.subscribeFeedReader': 'Suscribirse con lector de feeds (NetNewsWire, Reeder…)',
176+
'events.downloadRss': 'Abrir/descargar RSS',
177+
'events.subscribeOteReader': 'Suscribirse con OTE Reader',
178+
'events.previewOte': 'Vista previa en OTE Tools',
179+
'events.downloadJson': 'Abrir/descargar JSON',
168180

169181
// Footer
170182
'footer.description': 'Somos un colectivo que reúne a personas que dinamizan comunidades tech de habla hispana con el objetivo de ser un lugar de referencia donde se facilite la colaboración y el intercambio de experiencias.',

src/pages/Index.tsx

Lines changed: 118 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ const Index = () => {
2828
const eventsRssUrl = "https://combuilderses.github.io/events/feed.xml";
2929
const googleCalendarUrl = `https://calendar.google.com/calendar/render?cid=${encodeURIComponent(eventsIcsUrl)}`;
3030
const outlookCalendarUrl = `https://outlook.live.com/calendar/0/addcalendar?url=${encodeURIComponent(eventsIcsUrl)}&name=${encodeURIComponent("Community Builders Events")}`;
31+
const feedlyUrl = `https://www.feedly.com/home#subscription/feed/${eventsRssUrl}`;
32+
const feedProtocolUrl = eventsRssUrl.replace(/^https?:\/\//, 'feed://');
33+
const oteReaderUrl = `https://reader.opentechevents.org/?subscribe=${encodeURIComponent(eventsFeedUrl)}`;
34+
const otePreviewUrl = `https://tools.opentechevents.org/preview/?feed=${encodeURIComponent(eventsFeedUrl)}`;
3135
const [isMenuOpen, setIsMenuOpen] = useState(false);
3236
const [activeSection, setActiveSection] = useState('home');
3337
const [carouselApi, setCarouselApi] = useState<CarouselApi>();
@@ -39,8 +43,10 @@ const Index = () => {
3943
const [memberWindowStart, setMemberWindowStart] = useState(0);
4044
const [isMemberGridFading, setIsMemberGridFading] = useState(false);
4145
const [eventsLayout, setEventsLayout] = useState<'calendar' | 'list' | 'cards'>('calendar');
42-
const [isIcsMenuOpen, setIsIcsMenuOpen] = useState(false);
46+
const [openSubscribeMenu, setOpenSubscribeMenu] = useState<'ics' | 'rss' | 'ote' | null>(null);
4347
const icsMenuRef = useRef<HTMLDivElement>(null);
48+
const rssMenuRef = useRef<HTMLDivElement>(null);
49+
const oteMenuRef = useRef<HTMLDivElement>(null);
4450
const { t, language } = useLanguage();
4551

4652
// Fetch members data
@@ -171,30 +177,33 @@ const Index = () => {
171177
}, [members.length, showAllMembers]);
172178

173179
useEffect(() => {
174-
if (!isIcsMenuOpen) {
180+
if (!openSubscribeMenu) {
175181
return;
176182
}
177183

178-
const closeIcsMenu = (event: PointerEvent) => {
179-
if (!icsMenuRef.current?.contains(event.target as Node)) {
180-
setIsIcsMenuOpen(false);
184+
const subscribeMenuRefs = { ics: icsMenuRef, rss: rssMenuRef, ote: oteMenuRef };
185+
186+
const closeSubscribeMenu = (event: PointerEvent) => {
187+
const activeMenuRef = subscribeMenuRefs[openSubscribeMenu];
188+
if (!activeMenuRef.current?.contains(event.target as Node)) {
189+
setOpenSubscribeMenu(null);
181190
}
182191
};
183192

184-
const closeIcsMenuWithKeyboard = (event: KeyboardEvent) => {
193+
const closeSubscribeMenuWithKeyboard = (event: KeyboardEvent) => {
185194
if (event.key === 'Escape') {
186-
setIsIcsMenuOpen(false);
195+
setOpenSubscribeMenu(null);
187196
}
188197
};
189198

190-
document.addEventListener('pointerdown', closeIcsMenu);
191-
document.addEventListener('keydown', closeIcsMenuWithKeyboard);
199+
document.addEventListener('pointerdown', closeSubscribeMenu);
200+
document.addEventListener('keydown', closeSubscribeMenuWithKeyboard);
192201

193202
return () => {
194-
document.removeEventListener('pointerdown', closeIcsMenu);
195-
document.removeEventListener('keydown', closeIcsMenuWithKeyboard);
203+
document.removeEventListener('pointerdown', closeSubscribeMenu);
204+
document.removeEventListener('keydown', closeSubscribeMenuWithKeyboard);
196205
};
197-
}, [isIcsMenuOpen]);
206+
}, [openSubscribeMenu]);
198207

199208
const handleCarouselInteraction = (index: number) => {
200209
setIsAutoPlaying(false);
@@ -552,10 +561,10 @@ const Index = () => {
552561
<button
553562
type="button"
554563
aria-haspopup="menu"
555-
aria-expanded={isIcsMenuOpen}
564+
aria-expanded={openSubscribeMenu === 'ics'}
556565
onClick={(event) => {
557566
event.stopPropagation();
558-
setIsIcsMenuOpen((currentOpen) => !currentOpen);
567+
setOpenSubscribeMenu((current) => (current === 'ics' ? null : 'ics'));
559568
}}
560569
className="inline-flex h-5 cursor-pointer list-none items-center rounded-full leading-none transition-opacity hover:opacity-85"
561570
>
@@ -565,7 +574,7 @@ const Index = () => {
565574
className="block h-5 w-auto"
566575
/>
567576
</button>
568-
{isIcsMenuOpen && (
577+
{openSubscribeMenu === 'ics' && (
569578
<div className="absolute left-0 top-full z-50 mt-5 w-[min(14rem,calc(100vw-2rem))] rounded-lg border border-border bg-background p-2 shadow-xl sm:left-1/2 sm:-translate-x-1/2">
570579
<a
571580
href={googleCalendarUrl}
@@ -594,30 +603,102 @@ const Index = () => {
594603
</div>
595604
)}
596605
</div>
597-
<a
598-
href={eventsRssUrl}
599-
target="_blank"
600-
rel="noopener noreferrer"
601-
className="inline-flex rounded-full transition-opacity hover:opacity-85"
606+
<div
607+
ref={rssMenuRef}
608+
className="events-subscribe-menu relative inline-flex h-5 items-center leading-none"
602609
>
603-
<img
604-
src="./images/badge-rss.svg"
605-
alt="RSS feed"
606-
className="h-5 w-auto"
607-
/>
608-
</a>
609-
<a
610-
href={eventsFeedUrl}
611-
target="_blank"
612-
rel="noopener noreferrer"
613-
className="inline-flex rounded-full transition-opacity hover:opacity-85"
610+
<button
611+
type="button"
612+
aria-haspopup="menu"
613+
aria-expanded={openSubscribeMenu === 'rss'}
614+
onClick={(event) => {
615+
event.stopPropagation();
616+
setOpenSubscribeMenu((current) => (current === 'rss' ? null : 'rss'));
617+
}}
618+
className="inline-flex h-5 cursor-pointer list-none items-center rounded-full leading-none transition-opacity hover:opacity-85"
619+
>
620+
<img
621+
src="./images/badge-rss.svg"
622+
alt="RSS feed"
623+
className="block h-5 w-auto"
624+
/>
625+
</button>
626+
{openSubscribeMenu === 'rss' && (
627+
<div className="absolute left-0 top-full z-50 mt-5 w-[min(14rem,calc(100vw-2rem))] rounded-lg border border-border bg-background p-2 shadow-xl sm:left-1/2 sm:-translate-x-1/2">
628+
<a
629+
href={feedlyUrl}
630+
target="_blank"
631+
rel="noopener noreferrer"
632+
className="block rounded-md px-3 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-accent-foreground"
633+
>
634+
{t('events.subscribeFeedly')}
635+
</a>
636+
<a
637+
href={feedProtocolUrl}
638+
className="block rounded-md px-3 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-accent-foreground"
639+
>
640+
{t('events.subscribeFeedReader')}
641+
</a>
642+
<a
643+
href={eventsRssUrl}
644+
target="_blank"
645+
rel="noopener noreferrer"
646+
className="block rounded-md px-3 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-accent-foreground"
647+
>
648+
{t('events.downloadRss')}
649+
</a>
650+
</div>
651+
)}
652+
</div>
653+
<div
654+
ref={oteMenuRef}
655+
className="events-subscribe-menu relative inline-flex h-5 items-center leading-none"
614656
>
615-
<img
616-
src="https://opentechevents.org/badge/ote-feed.svg"
617-
alt="OTE feed"
618-
className="h-5 w-auto"
619-
/>
620-
</a>
657+
<button
658+
type="button"
659+
aria-haspopup="menu"
660+
aria-expanded={openSubscribeMenu === 'ote'}
661+
onClick={(event) => {
662+
event.stopPropagation();
663+
setOpenSubscribeMenu((current) => (current === 'ote' ? null : 'ote'));
664+
}}
665+
className="inline-flex h-5 cursor-pointer list-none items-center rounded-full leading-none transition-opacity hover:opacity-85"
666+
>
667+
<img
668+
src="https://opentechevents.org/badge/ote-feed.svg"
669+
alt="OTE feed"
670+
className="block h-5 w-auto"
671+
/>
672+
</button>
673+
{openSubscribeMenu === 'ote' && (
674+
<div className="absolute left-0 top-full z-50 mt-5 w-[min(14rem,calc(100vw-2rem))] rounded-lg border border-border bg-background p-2 shadow-xl sm:left-1/2 sm:-translate-x-1/2">
675+
<a
676+
href={oteReaderUrl}
677+
target="_blank"
678+
rel="noopener noreferrer"
679+
className="block rounded-md px-3 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-accent-foreground"
680+
>
681+
{t('events.subscribeOteReader')}
682+
</a>
683+
<a
684+
href={otePreviewUrl}
685+
target="_blank"
686+
rel="noopener noreferrer"
687+
className="block rounded-md px-3 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-accent-foreground"
688+
>
689+
{t('events.previewOte')}
690+
</a>
691+
<a
692+
href={eventsFeedUrl}
693+
target="_blank"
694+
rel="noopener noreferrer"
695+
className="block rounded-md px-3 py-2 text-sm text-muted-foreground hover:bg-accent hover:text-accent-foreground"
696+
>
697+
{t('events.downloadJson')}
698+
</a>
699+
</div>
700+
)}
701+
</div>
621702
</div>
622703
</div>
623704
</div>

0 commit comments

Comments
 (0)