diff --git a/apps/web/app/orgs/[orgslug]/layout.tsx b/apps/web/app/orgs/[orgslug]/layout.tsx index de924c3d..56735360 100644 --- a/apps/web/app/orgs/[orgslug]/layout.tsx +++ b/apps/web/app/orgs/[orgslug]/layout.tsx @@ -3,6 +3,7 @@ import { OrgProvider } from '@components/Contexts/OrgContext' import NextTopLoader from 'nextjs-toploader'; import Toast from '@components/StyledElements/Toast/Toast' import '@styles/globals.css' +import Onboarding from '@components/Onboarding/Onboarding'; export default function RootLayout({ children, @@ -16,6 +17,7 @@ export default function RootLayout({ + {children} diff --git a/apps/web/components/Contexts/CourseContext.tsx b/apps/web/components/Contexts/CourseContext.tsx index f407cd00..d33bf38d 100644 --- a/apps/web/components/Contexts/CourseContext.tsx +++ b/apps/web/components/Contexts/CourseContext.tsx @@ -4,7 +4,6 @@ import { swrFetcher } from '@services/utils/ts/requests' import React, { createContext, useContext, useEffect, useReducer } from 'react' import useSWR from 'swr' import { useLHSession } from '@components/Contexts/LHSessionContext' -import PageLoading from '@components/Objects/Loaders/PageLoading' export const CourseContext = createContext(null) export const CourseDispatchContext = createContext(null) diff --git a/apps/web/components/Onboarding/Onboarding.tsx b/apps/web/components/Onboarding/Onboarding.tsx new file mode 100644 index 00000000..626eb83a --- /dev/null +++ b/apps/web/components/Onboarding/Onboarding.tsx @@ -0,0 +1,277 @@ +import Modal from '@components/StyledElements/Modal/Modal'; +import Image, { StaticImageData } from 'next/image'; +import React, { useEffect, useState } from 'react'; +import OnBoardWelcome from '@public/onboarding/OnBoardWelcome.png'; +import OnBoardCourses from '@public/onboarding/OnBoardCourses.png'; +import OnBoardActivities from '@public/onboarding/OnBoardActivities.png'; +import OnBoardEditor from '@public/onboarding/OnBoardEditor.png'; +import OnBoardAI from '@public/onboarding/OnBoardAI.png'; +import OnBoardUGs from '@public/onboarding/OnBoardUGs.png'; +import OnBoardAccess from '@public/onboarding/OnBoardAccess.png'; +import OnBoardMore from '@public/onboarding/OnBoardMore.png'; +import { ArrowRight, Book, Check, Globe, Info, PictureInPicture, Sparkle, Sprout, SquareUser, Users } from 'lucide-react'; +import { useRouter } from 'next/navigation'; +import { getUriWithOrg } from '@services/config/config'; +import { useOrg } from '@components/Contexts/OrgContext'; +import useAdminStatus from '@components/Hooks/useAdminStatus'; + +interface OnboardingStep { + imageSrc: StaticImageData; + title: string; + description: string; + buttons?: { + label: string; + action: () => void; + icon?: React.ReactNode; + }[]; +} + +const Onboarding: React.FC = () => { + const [currentStep, setCurrentStep] = useState(0); + const [isModalOpen, setIsModalOpen] = useState(false); + const [isOnboardingComplete, setIsOnboardingComplete] = useState(true); + const router = useRouter(); + const org = useOrg() as any; + const isUserAdmin = useAdminStatus() as any; + + const onboardingData: OnboardingStep[] = [ + { + imageSrc: OnBoardWelcome, + title: 'Teach the world!', + description: 'Welcome to LearnHouse, a LMS engineered for simplicity, ease of use and performance, meet the new way to create, share, and engage with educational content.', + }, + { + imageSrc: OnBoardCourses, + title: 'Create Courses', + description: 'Courses are the main building blocks of LearnHouse, they always contain Chapters and Chapters contain Activities.', + buttons: [ + { + label: 'Create New Course', + action: () => router.push(getUriWithOrg(org?.slug, '/courses?new=true')), + icon: , + }, + ], + }, + { + imageSrc: OnBoardActivities, + title: 'Activities', + description: 'Activities are elements you can add to your Courses via Chapters, they can be : Dynamic Pages, Videos, Documents, Quizz and more soon.', + buttons: [ + { + label: 'Learn more about activities', + action: () => window.open('https://university.learnhouse.io/course/be89716c-9992-44bb-81df-ef3d76e355ba', '_blank'), + icon: , + }, + ], + }, + { + imageSrc: OnBoardEditor, + title: 'Dynamic pages and The Editor', + description: 'Dynamic pages are pages with dynamic content, like Notion pages they can contain various components like Quizzes, Images, Videos, Documents etc', + buttons: [ + { + label: 'Learn more about Dynamic Pages and The Editor', + action: () => window.open('https://university.learnhouse.io/course/be89716c-9992-44bb-81df-ef3d76e355ba', '_blank'), + icon: , + }, + ], + }, + { + imageSrc: OnBoardAI, + title: 'Artificial Intelligence', + description: 'Tools for tought made for teachers and students alike, context aware it can reply based on your courses and the unique content you create on LearnHouse', + buttons: [ + { + label: 'Learn more about LearnHouse AI', + action: () => window.open('https://docs.learnhouse.app/features/ai/students', '_blank'), + icon: , + }, + ], + }, + { + imageSrc: OnBoardUGs, + title: 'Group students and streamline access ', + description: 'With UserGroups you can separate students by Groups and give access to Courses depending on their needs', + buttons: [ + { + label: 'Create UserGroups', + action: () => router.push(getUriWithOrg(org?.slug, '/dash/users/settings/usergroups')), + icon: , + }, + ], + }, + { + imageSrc: OnBoardAccess, + title: 'Choose whether to make Courses available on the Web or not ', + description: 'You can choose to make your Courses discoverable from search engines and accesible to non authenticated users or to only give it to authenticated Users', + buttons: [ + + ], + }, + { + imageSrc: OnBoardMore, + title: 'To infinity and beyond', + description: "To Learn more about LearnHouse, you're welcome to follow our Original courses on the LearnHouse University", + buttons: [ + { + label: 'LearnHouse University', + action: () => window.open('https://university.learnhouse.io', '_blank'), + icon: , + }, + ], + }, + ]; + + useEffect(() => { + // Check if onboarding is already completed in local storage + const isOnboardingCompleted = localStorage.getItem('isOnboardingCompleted'); + setIsOnboardingComplete(isOnboardingCompleted === 'true'); + setIsModalOpen(!isOnboardingCompleted); // Show modal if onboarding is not completed + }, []); + + const nextStep = () => { + if (currentStep < onboardingData.length - 1) { + setCurrentStep(currentStep + 1); + } else { + // Mark onboarding as completed in local storage + localStorage.setItem('isOnboardingCompleted', 'true'); + setIsModalOpen(false); // Close modal after completion + setIsOnboardingComplete(true); // Show success message + console.log('Onboarding completed'); + } + }; + + const skipOnboarding = () => { + // Mark onboarding as completed in local storage + localStorage.setItem('isOnboardingCompleted', 'true'); + setIsModalOpen(false); // Close modal after skipping + console.log('Onboarding skipped'); + }; + + const goToStep = (index: number) => { + if (index >= 0 && index < onboardingData.length) { + setCurrentStep(index); + } + }; + + return ( +
+ {isUserAdmin.isAdmin && !isUserAdmin.loading && !isOnboardingComplete && + } + dialogTrigger={ + +
+
+ +

Onboarding

+
+
+
+ } + />} +
+ ); +}; + +interface OnboardingScreenProps { + step: OnboardingStep; + currentStep: number; + nextStep: () => void; + skipOnboarding: () => void; + goToStep: (index: number) => void; + setIsModalOpen: (value: boolean) => void; + onboardingData: OnboardingStep[]; +} + +const OnboardingScreen: React.FC = ({ + step, + currentStep, + nextStep, + skipOnboarding, + goToStep, + onboardingData, + setIsModalOpen, +}) => { + const isLastStep = currentStep === onboardingData.length - 1; + + return ( +
+
+
+ +
+
+ {onboardingData.map((_, index) => ( +
goToStep(index)} + className={`h-[7px] w-auto ${index === currentStep ? 'bg-black' : 'bg-gray-300'} hover:bg-gray-700 rounded-lg shadow-md cursor-pointer`} + >
+ ))} +
+
+
+

{step.title}

+

{step.description}

+
+
+
+
+
setIsModalOpen(false)} + > + +
+
+
+ {step.buttons?.map((button, index) => ( +
+

{button.label}

+ {button.icon} +
+ ))} + {isLastStep ? ( +
+

Finish Onboarding

+ +
+ ) : ( +
+

Next

+ +
+ )} +
+
+
+
+ ); +}; + +export default Onboarding; \ No newline at end of file diff --git a/apps/web/components/StyledElements/Modal/Modal.tsx b/apps/web/components/StyledElements/Modal/Modal.tsx index 51e12678..0b1ca9c7 100644 --- a/apps/web/components/StyledElements/Modal/Modal.tsx +++ b/apps/web/components/StyledElements/Modal/Modal.tsx @@ -6,8 +6,8 @@ import { blackA, mauve } from '@radix-ui/colors' import { ButtonBlack } from '../Form/Form' type ModalParams = { - dialogTitle: string - dialogDescription: string + dialogTitle?: string + dialogDescription?: string dialogContent: React.ReactNode dialogClose?: React.ReactNode | null dialogTrigger?: React.ReactNode @@ -16,6 +16,8 @@ type ModalParams = { isDialogOpen?: boolean minHeight?: 'sm' | 'md' | 'lg' | 'xl' | 'no-min' minWidth?: 'sm' | 'md' | 'lg' | 'xl' | 'no-min' + customHeight?: string + customWidth?: string } const Modal = (params: ModalParams) => ( @@ -30,11 +32,14 @@ const Modal = (params: ModalParams) => ( className="overflow-auto scrollbar-w-2 scrollbar-h-2 scrollbar scrollbar-thumb-black/20 scrollbar-thumb-rounded-full scrollbar-track-rounded-full" minHeight={params.minHeight} minWidth={params.minWidth} + > - - {params.dialogTitle} - {params.dialogDescription} - + {params.dialogTitle && params.dialogDescription && + + {params.dialogTitle} + {params.dialogDescription} + + } {params.dialogContent} {params.dialogClose ? ( diff --git a/apps/web/public/onboarding/OnBoardAI.png b/apps/web/public/onboarding/OnBoardAI.png new file mode 100644 index 00000000..8db2dbe3 Binary files /dev/null and b/apps/web/public/onboarding/OnBoardAI.png differ diff --git a/apps/web/public/onboarding/OnBoardAccess.png b/apps/web/public/onboarding/OnBoardAccess.png new file mode 100644 index 00000000..e24be428 Binary files /dev/null and b/apps/web/public/onboarding/OnBoardAccess.png differ diff --git a/apps/web/public/onboarding/OnBoardActivities.png b/apps/web/public/onboarding/OnBoardActivities.png new file mode 100644 index 00000000..2456da88 Binary files /dev/null and b/apps/web/public/onboarding/OnBoardActivities.png differ diff --git a/apps/web/public/onboarding/OnBoardCourses.png b/apps/web/public/onboarding/OnBoardCourses.png new file mode 100644 index 00000000..7ac2e7ff Binary files /dev/null and b/apps/web/public/onboarding/OnBoardCourses.png differ diff --git a/apps/web/public/onboarding/OnBoardEditor.png b/apps/web/public/onboarding/OnBoardEditor.png new file mode 100644 index 00000000..fd986665 Binary files /dev/null and b/apps/web/public/onboarding/OnBoardEditor.png differ diff --git a/apps/web/public/onboarding/OnBoardMore.png b/apps/web/public/onboarding/OnBoardMore.png new file mode 100644 index 00000000..37db9869 Binary files /dev/null and b/apps/web/public/onboarding/OnBoardMore.png differ diff --git a/apps/web/public/onboarding/OnBoardUGs.png b/apps/web/public/onboarding/OnBoardUGs.png new file mode 100644 index 00000000..a2895c07 Binary files /dev/null and b/apps/web/public/onboarding/OnBoardUGs.png differ diff --git a/apps/web/public/onboarding/OnBoardWelcome.png b/apps/web/public/onboarding/OnBoardWelcome.png new file mode 100644 index 00000000..efe46a0d Binary files /dev/null and b/apps/web/public/onboarding/OnBoardWelcome.png differ