diff --git a/src/components/DaySelect/time_select_component/time_select_component.tsx b/src/components/DaySelect/time_select_component/time_select_component.tsx index 4578b10b..ec11e67e 100644 --- a/src/components/DaySelect/time_select_component/time_select_component.tsx +++ b/src/components/DaySelect/time_select_component/time_select_component.tsx @@ -55,7 +55,7 @@ export const TimeSelectComponent = (props: any) => { return (

diff --git a/src/components/Home/HomePage.tsx b/src/components/Home/HomePage.tsx index 5b5c8557..164dfd1e 100644 --- a/src/components/Home/HomePage.tsx +++ b/src/components/Home/HomePage.tsx @@ -7,6 +7,7 @@ import graphic from './calendargraphic.png'; import LoginPopup from '../utils/components/LoginPopup'; import Footer from '../utils/components/Footer'; import Button from '../utils/components/Button'; +import TutorialModal from '../utils/components/TutorialModal/TutorialModal'; // import { SiGooglecalendar } from 'react-icons/si'; // import { FaLock } from 'react-icons/fa'; @@ -72,10 +73,19 @@ export default function HomePage() { } }; + const [showTutorial, setTutorial] = React.useState(false); + return (

+ {showTutorial && ( + setTutorial(false)} + /> + )} +
@@ -127,6 +137,16 @@ export default function HomePage() { View My Events
+ +

+ New to ymeets? → + setTutorial(true)}> + Click here for a quick walkthrough! + +

+
diff --git a/src/components/navbar/NavBar.tsx b/src/components/navbar/NavBar.tsx index b78509cc..d3d5c6e7 100644 --- a/src/components/navbar/NavBar.tsx +++ b/src/components/navbar/NavBar.tsx @@ -5,6 +5,7 @@ import { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { onAuthStateChanged } from 'firebase/auth'; import { auth } from '../../backend/firebase'; +import TutorialModal from '../utils/components/TutorialModal/TutorialModal'; import { IconMenu2, @@ -19,6 +20,7 @@ import { IconMoonFilled, IconMoon, IconSun, + IconBook, } from '@tabler/icons-react'; import { useAuth } from '../../backend/authContext'; import { useTheme } from '../../contexts/ThemeContext'; @@ -34,6 +36,8 @@ export default function NavBar() { const { login, logout, currentUser } = useAuth(); + const [showTutorial, setTutorial] = useState(false); + const handleMouseLeave = () => { setIsOpen(false); }; @@ -82,6 +86,13 @@ export default function NavBar() { return ( <> + {showTutorial && ( + setTutorial(false)} + /> + )} +
@@ -148,6 +159,20 @@ export default function NavBar() { aria-orientation="vertical" aria-labelledby="options-menu" > + { + e.preventDefault(); + setTutorial(true); + setMenuState('closed'); + }} + > + Tutorial + + +
+ void; +} + +interface TutorialSlide { + id: string; + title: string; + description: string; + icon?: React.ReactNode; + description2?: string; + media?: { type: 'video' | 'image'; src: string; alt: string }; + endnote?: string; +} + +/* +adding video: +media: { + type: 'video', + src: '___.mp4', + alt: 'Step X Demonstration' + }, + +adding image/gif: +media: { + type: 'image', + src: '___.png', + alt: 'Step X Demonstration' + }, + + + */ + +const tutorialSlides: TutorialSlide[] = [ + { + id: 'welcome', + title: 'Welcome to ymeets!', + description: "Thank you for visting our website! In this quick walkthrough, you'll learn how to create & schedule events, share them with others, view availability, and more.", + icon: , + endnote: "Let's get started!", + media: { + type: 'video', + src: step1media, + alt: 'Step 1 Demonstration' + }, + }, + { + id: 'step2', + title: 'Creating Events', + description: 'To make an event, you can press the "Create Event" button on the homepage. That takes you to our Day Selection page, where you can freely customize your event name, description, timezone, and location.', + icon: , + media: { + type: 'image', + src: step2media, + alt: 'Step 2 Demonstration' + }, + }, + { + id: 'step3', + title: 'Selecting Specific Dates', + description: 'Under the "Specific Days" tab, you can interact with the calendar to freely customize the month, timespan, and specific dates of your event. ', + icon: , + media: { + type: 'image', + src: step3media, + alt: 'Step 3 Demonstration' + }, + }, + { + id: 'step4', + title: 'Selecting General Days', + description: 'If you have a repeating event, you can instead navigate to the "General Days" tab! Here, you can pick a general day and time for your event.', + icon: , + media: { + type: 'image', + src: step4media, + alt: 'Step 4 Demonstration' + }, + }, + { + id: 'step5', + title: 'Sharing Events', + description: "Now, upon pressing the 'Create' button, you'll be guided to your event dashboard! Here, you can share your event by pressing the 'Copy Link' button.", + icon: , + media: { + type: 'video', + src: step5media, + alt: 'Step 5 Demonstration' + }, + description2: 'Also, if you press the "View Availabilities" button, you can find our "AutoDraft Email" feature which instantly drafts a template email for you to share!', + }, + { + id: 'step6', + title: 'Setting Availability', + description: 'After receiving the link, your participants can add their availability by pressing the "Edit Availability" button. They can either fill it in manually, or autofill from Google Calendar!', + icon: , + media: { + type: 'video', + src: step6media, + alt: 'Step 6 Demonstration' + }, + }, + { + id: 'step7', + title: 'Scheduling an Event', + description: "After everyone has filled in their availabilities, you can see when they overlap and select a time that works best! You can also hover over a participant's name to see their specific availability.", + icon: , + media: { + type: 'video', + src: step7media, + alt: 'Step 7 Demonstration' + }, + description2: "Then, you can schedule the event directly into Google Calendar via the 'Export to GCal' button!", + }, + { + id: 'step8', + title: 'Wrapping Up', + description: 'Lastly, you can view, delete, and copy the links for all of your created & participated-in events by pressing the "My Events" button in the top right corner.', + icon: , + endnote: "Thanks for viewing the tutorial! 👋", + media: { + type: 'video', + src: step8media, + alt: 'Step 8 Demonstration' + }, + }, +]; + +export default function TutorialModal({ + isOpen, + onClose, +}: TutorialProps) { + const [isVisible, setIsVisible] = useState(false); + const [isAnimating, setIsAnimating] = useState(false); + + const [currentSlide, setCurrentSlide] = useState(0); + const [slideDirection, setSlideDirection] = useState<'left' | 'right'>('right'); + + const totalSlides = tutorialSlides.length; + const isFirstSlide = currentSlide === 0; + const isLastSlide = currentSlide === totalSlides - 1; + + useEffect(() => { + if (isOpen) { + setIsVisible(true); + setCurrentSlide(0); + document.body.style.overflow = 'hidden'; + setTimeout(() => setIsAnimating(true), 10); + } else if (isVisible) { + setIsAnimating(false); + const timer = setTimeout(() => { + setIsVisible(false); + document.body.style.overflow = ''; + }, 300); + return () => clearTimeout(timer); + } + }, [isOpen, isVisible]); + + const handleNext = () => { + if (!isLastSlide) { + setSlideDirection('right'); + setCurrentSlide((prev) => prev + 1); + } else { + // Last slide - close modal and mark tutorial as complete + localStorage.setItem('hasSeenCreatorTutorial', 'true'); + onClose(); + } + }; + + const handlePrevious = () => { + if (!isFirstSlide) { + setSlideDirection('left'); + setCurrentSlide((prev) => prev - 1); + } + }; + + const handleSkip = () => { + localStorage.setItem('hasSeenCreatorTutorial', 'true'); + onClose(); + }; + + const currentSlideData = tutorialSlides[currentSlide]; + + if (!isVisible) return null; + + return ( +
+
+ +
+ + +
+
+
+
+ {currentSlideData.icon} +
+
+

+ {currentSlideData.title} +

+
+ +

+ {currentSlideData.description} +

+ +
+ {currentSlideData.media ? ( + currentSlideData.media.type === 'video' ? ( + + ) : ( + {currentSlideData.media.alt} + ) + ) : ( +

+ [media placeholder] +

+ )} +
+ + {currentSlideData.description2 && ( +

+ {currentSlideData.description2} +

+ )} + + {currentSlideData.endnote && ( +

+ {currentSlideData.endnote} +

+ )} + +
+ {tutorialSlides.map((_, index) => ( +
+ +
+ + + {!isLastSlide && ( + + )} + + +
+ +
+ Step {currentSlide + 1} of {totalSlides} +
+ +
+
+
+ ); +} \ No newline at end of file diff --git a/src/components/utils/components/TutorialModal/ViewAvailable.mp4 b/src/components/utils/components/TutorialModal/ViewAvailable.mp4 new file mode 100644 index 00000000..d0832e8f Binary files /dev/null and b/src/components/utils/components/TutorialModal/ViewAvailable.mp4 differ diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts index 9bb86ecd..40e1f705 100644 --- a/src/react-app-env.d.ts +++ b/src/react-app-env.d.ts @@ -6,3 +6,8 @@ declare global { FB: typeof FB } } + +declare module '*.mp4' { + const src: string; + export default src; +} \ No newline at end of file