diff --git a/nx-dev/nx-dev/pages/challenge.tsx b/nx-dev/nx-dev/pages/challenge.tsx
index 25ac135333ce3..2a0538fd87c9d 100644
--- a/nx-dev/nx-dev/pages/challenge.tsx
+++ b/nx-dev/nx-dev/pages/challenge.tsx
@@ -4,20 +4,207 @@ import {
FlipCardBackYoutube,
Footer,
Header,
+ Modal,
+ ModalHeader,
SectionHeading,
+ YouTube,
} from '@nx/nx-dev/ui-common';
-import { YouTube } from '@nx/nx-dev/ui-common';
import { NextSeo } from 'next-seo';
import Link from 'next/link';
import { useRouter } from 'next/router';
+import { ReactNode, useEffect, useState } from 'react';
-export default function Community(): JSX.Element {
+interface NewYearTip {
+ day: number;
+ cardBack: ReactNode;
+ modalHeader: ReactNode;
+ modalContent: ReactNode;
+}
+
+const tips: NewYearTip[] = [
+ {
+ day: 1,
+ cardBack: (
+
+ ),
+ modalHeader:
January 1st, 2024
,
+ modalContent: (
+ <>
+
+ What is Nx?
+
+
+
+
+ Here is some text about what Nx is
+
+ Read more in our{' '}
+
+ Intro to Nx
+
+
+ >
+ ),
+ },
+ {
+ day: 2,
+ cardBack: (
+
+ ),
+ modalHeader: January 2nd, 2024
,
+ modalContent: (
+ <>
+
+ Which Style of Workspace is Right for You?
+
+
+
+
+ Here is some text about ways to use Nx
+
+ Read more in our{' '}
+
+ Types of Repos Guide
+
+
+ >
+ ),
+ },
+ {
+ day: 3,
+ cardBack: (
+
+ ),
+ modalHeader: January 3rd, 2024
,
+ modalContent: ,
+ },
+ {
+ day: 4,
+ cardBack: (
+
+ Add Nx to an Existing Project
+
+ ),
+ modalHeader: January 4th, 2024
,
+ modalContent: ,
+ },
+ {
+ day: 5,
+ cardBack: (
+
+ ),
+ modalHeader: January 5th, 2024
,
+ modalContent: ,
+ },
+ {
+ day: 8,
+ cardBack: (
+
+ Explore Example Repos
+
+ ),
+ modalHeader: January 8th, 2024
,
+ modalContent: ,
+ },
+ {
+ day: 9,
+ cardBack: (
+
+ Let's Build a CLI
+
+ ),
+ modalHeader: January 9th, 2024
,
+ modalContent: ,
+ },
+ {
+ day: 10,
+ cardBack: (
+
+ Optimizing Your CI/CD
+
+ ),
+ modalHeader: January 10th, 2024
,
+ modalContent: ,
+ },
+ {
+ day: 11,
+ cardBack: (
+
+ ),
+ modalHeader: January 11th, 2024
,
+ modalContent: ,
+ },
+ {
+ day: 12,
+ cardBack: (
+
+ ),
+ modalHeader: January 12th, 2024
,
+ modalContent: ,
+ },
+];
+
+export default function NewYear(): JSX.Element {
+ const currentDay =
+ new Date().getFullYear() === 2023 ? new Date().getDate() : 0;
const router = useRouter();
+ const [cards, setCards] = useState({});
+ const [currentModal, setCurrentModal] = useState(0);
+
+ useEffect(() => {
+ const cards = JSON.parse(localStorage.getItem('cards') || '{}');
+ if (cards) {
+ setCards(cards);
+ }
+ }, []);
+ useEffect(() => {
+ localStorage.setItem('cards', JSON.stringify(cards));
+ }, [cards]);
+ const onFlip = (text, isFlipped) => {
+ setCards({ ...cards, [text]: isFlipped });
+ };
+ function getIsFlippable(day: number) {
+ return currentDay >= day;
+ }
return (
<>
-
+
@@ -54,123 +241,34 @@ export default function Community(): JSX.Element {
-
-
-
-
-
-
-
-
-
-
-
-
- Add Nx to an Existing Project
-
-
-
-
-
-
-
-
- Explore Example Repos
-
-
-
-
- Let's Build a CLI
-
-
-
-
- Optimizing Your CI/CD
-
-
-
-
-
-
-
-
+ {tips.map((tip) => (
+ setCurrentModal(tip.day)}
+ >
+ {tip.cardBack}
+
+ ))}
-
-
-
-
- January 1st, 2024
-
-
- What is Nx?
-
-
-
-
-
Here is some text about what Nx is
-
- Read more in our{' '}
-
- Intro to Nx
-
-
-
-
-
- January 2nd, 2024
-
-
- Which Style of Workspace is Right for You?
-
-
-
-
-
Here is some text about ways to use Nx
-
- Read more in our{' '}
-
- Types of Repos Guide
-
-
-
-
-
+
+ {tips.map((tip) => (
+
{
+ setCurrentModal(0);
+ }}
+ >
+ {tip.modalHeader}
+ {tip.modalContent}
+
+ ))}
diff --git a/nx-dev/ui-common/src/index.ts b/nx-dev/ui-common/src/index.ts
index ba6f6bbf74cc8..d3b9846d35305 100644
--- a/nx-dev/ui-common/src/index.ts
+++ b/nx-dev/ui-common/src/index.ts
@@ -7,6 +7,7 @@ export * from './lib/champion-perks';
export * from './lib/header';
export * from './lib/flip-card';
export * from './lib/footer';
+export * from './lib/modal';
export * from './lib/selector';
export * from './lib/sidebar-container';
export * from './lib/sidebar';
diff --git a/nx-dev/ui-common/src/lib/modal.tsx b/nx-dev/ui-common/src/lib/modal.tsx
new file mode 100644
index 0000000000000..fed763bf8019f
--- /dev/null
+++ b/nx-dev/ui-common/src/lib/modal.tsx
@@ -0,0 +1,106 @@
+'use client';
+
+import { XMarkIcon } from '@heroicons/react/24/outline';
+import cx from 'classnames';
+import { ReactNode, useEffect, useRef, useState } from 'react';
+
+function useEventListener(
+ eventName: string,
+ handler: Function,
+ element = globalThis
+) {
+ // Create a ref that stores handler
+ const savedHandler = useRef
();
+
+ // Update ref.current value if handler changes.
+ // This allows our effect below to always get latest handler ...
+ // ... without us needing to pass it in effect deps array ...
+ // ... and potentially cause effect to re-run every render.
+ useEffect(() => {
+ savedHandler.current = handler;
+ }, [handler]);
+
+ useEffect(
+ () => {
+ // Make sure element supports addEventListener
+ // On
+ const isSupported = element && element.addEventListener;
+ if (!isSupported) return;
+
+ // Create event listener that calls handler function stored in ref
+ const eventListener = (event: Event) =>
+ savedHandler.current && savedHandler.current(event);
+
+ // Add event listener
+ element.addEventListener(eventName, eventListener);
+
+ // Remove event listener on cleanup
+ return () => {
+ element.removeEventListener(eventName, eventListener);
+ };
+ },
+ [eventName, element] // Re-run if eventName or element changes
+ );
+}
+
+export function ModalHeader({
+ onClose,
+ children,
+}: {
+ onClose?: () => void;
+ children: ReactNode;
+}) {
+ return (
+
+
{children}
+
+
+ );
+}
+export function ModalFooter({ children }: { children: ReactNode }) {
+ return (
+
+ {children}
+
+ );
+}
+
+export function Modal({
+ isOpen,
+ onClose,
+ children,
+}: {
+ isOpen?: boolean;
+ onClose?: () => void;
+ children: ReactNode;
+}) {
+ useEventListener('keydown', (event: KeyboardEvent) => {
+ if (event.key === '27' || event.key === 'Escape') {
+ onClose && onClose();
+ }
+ });
+ return (
+
+ );
+}