chore: refactor frontend components folder

This commit is contained in:
swve 2024-11-25 23:26:33 +01:00
parent 46f016f661
commit 5a746a946d
106 changed files with 159 additions and 164 deletions

View file

@ -15,7 +15,7 @@ import {
useAIChatBot,
useAIChatBotDispatch,
} from '@components/Contexts/AI/AIChatBotContext'
import useGetAIFeatures from '../../../AI/Hooks/useGetAIFeatures'
import useGetAIFeatures from '../../../Hooks/useGetAIFeatures'
import UserAvatar from '@components/Objects/UserAvatar'
type AIActivityAskProps = {

View file

@ -4,7 +4,7 @@ import learnhouseAI_icon from 'public/learnhouse_ai_simple.png'
import Image from 'next/image'
import { BookOpen, FormInput, Languages, MoreVertical } from 'lucide-react'
import { BubbleMenu } from '@tiptap/react'
import ToolTip from '@components/StyledElements/Tooltip/Tooltip'
import ToolTip from '@components/Objects/StyledElements/Tooltip/Tooltip'
import {
AIChatBotStateTypes,
useAIChatBot,
@ -14,7 +14,7 @@ import {
sendActivityAIChatMessage,
startActivityAIChatSession,
} from '@services/ai/ai'
import useGetAIFeatures from '../../../../AI/Hooks/useGetAIFeatures'
import useGetAIFeatures from '../../../../Hooks/useGetAIFeatures'
import { useLHSession } from '@components/Contexts/LHSessionContext'
type AICanvaToolkitProps = {

View file

@ -0,0 +1,14 @@
'use client';
import React from 'react'
import useAdminStatus from '../Hooks/useAdminStatus'
// Terrible name and terible implementation, need to be refactored asap
function ContentPlaceHolderIfUserIsNotAdmin({ text }: { text: string }) {
const isUserAdmin = useAdminStatus() as any
return (
<span>{isUserAdmin ? text : 'No content yet'}</span>
)
}
export default ContentPlaceHolderIfUserIsNotAdmin

View file

@ -4,8 +4,8 @@ import { useLHSession } from '@components/Contexts/LHSessionContext'
import useSWR from 'swr'
import { getProductsByCourse, getStripeProductCheckoutSession } from '@services/payments/products'
import { RefreshCcw, SquareCheck, ChevronDown, ChevronUp } from 'lucide-react'
import { Badge } from '@components/ui/badge'
import { Button } from '@components/ui/button'
import { Badge } from '@components/Ui/badge'
import { Button } from '@components/Ui/button'
import toast from 'react-hot-toast'
import { useRouter } from 'next/navigation'
import { getUriWithOrg } from '@services/config/config'

View file

@ -9,7 +9,7 @@ import { useMediaQuery } from 'usehooks-ts'
import { getUriWithOrg } from '@services/config/config'
import { getProductsByCourse } from '@services/payments/products'
import { LogIn, LogOut, ShoppingCart, AlertCircle } from 'lucide-react'
import Modal from '@components/StyledElements/Modal/Modal'
import Modal from '@components/Objects/StyledElements/Modal/Modal'
import CoursePaidOptions from './CoursePaidOptions'
import { checkPaidAccess } from '@services/payments/payments'

View file

@ -8,7 +8,7 @@ import FormLayout, {
FormLabelAndMessage,
Input,
Textarea,
} from '@components/StyledElements/Form/Form'
} from '@components/Objects/StyledElements/Form/Form'
import { useCourse } from '@components/Contexts/CourseContext'
import useSWR, { mutate } from 'swr'
import { getAPIUrl } from '@services/config/config'
@ -17,7 +17,7 @@ import useAdminStatus from '@components/Hooks/useAdminStatus'
import { useOrg } from '@components/Contexts/OrgContext'
import { createCourseUpdate, deleteCourseUpdate } from '@services/courses/updates'
import toast from 'react-hot-toast'
import ConfirmationModal from '@components/StyledElements/ConfirmationModal/ConfirmationModal'
import ConfirmationModal from '@components/Objects/StyledElements/ConfirmationModal/ConfirmationModal'
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
import { useLHSession } from '@components/Contexts/LHSessionContext'

View file

@ -23,7 +23,7 @@ import {
sendActivityAIChatMessage,
startActivityAIChatSession,
} from '@services/ai/ai'
import useGetAIFeatures from '@components/AI/Hooks/useGetAIFeatures'
import useGetAIFeatures from '@components/Hooks/useGetAIFeatures'
import { useLHSession } from '@components/Contexts/LHSessionContext'
type AIEditorToolkitProps = {

View file

@ -29,7 +29,7 @@ import Table from '@tiptap/extension-table'
import TableCell from '@tiptap/extension-table-cell'
import TableHeader from '@tiptap/extension-table-header'
import TableRow from '@tiptap/extension-table-row'
import ToolTip from '@components/StyledElements/Tooltip/Tooltip'
import ToolTip from '@components/Objects/StyledElements/Tooltip/Tooltip'
import Link from 'next/link'
import { getCourseThumbnailMediaDirectory } from '@services/media/media'
@ -47,7 +47,7 @@ import java from 'highlight.js/lib/languages/java'
import { CourseProvider } from '@components/Contexts/CourseContext'
import { useLHSession } from '@components/Contexts/LHSessionContext'
import AIEditorToolkit from './AI/AIEditorToolkit'
import useGetAIFeatures from '@components/AI/Hooks/useGetAIFeatures'
import useGetAIFeatures from '@components/Hooks/useGetAIFeatures'
import Collaboration from '@tiptap/extension-collaboration'
import CollaborationCursor from '@tiptap/extension-collaboration-cursor'
import ActiveAvatars from './ActiveAvatars'

View file

@ -3,7 +3,7 @@ import { default as React, useEffect, useRef, useState } from 'react'
import Editor from './Editor'
import { updateActivity } from '@services/courses/activities'
import { toast } from 'react-hot-toast'
import Toast from '@components/StyledElements/Toast/Toast'
import Toast from '@components/Objects/StyledElements/Toast/Toast'
import { OrgProvider } from '@components/Contexts/OrgContext'
import { useLHSession } from '@components/Contexts/LHSessionContext'

View file

@ -29,7 +29,7 @@ import {
Video,
} from 'lucide-react'
import { SiYoutube } from '@icons-pack/react-simple-icons'
import ToolTip from '@components/StyledElements/Tooltip/Tooltip'
import ToolTip from '@components/Objects/StyledElements/Tooltip/Tooltip'
export const ToolbarButtons = ({ editor, props }: any) => {
if (!editor) {

View file

@ -7,7 +7,7 @@ import FormLayout, {
FormMessage,
Input,
} from '@components/StyledElements/Form/Form'
} from '@components/Objects/StyledElements/Form/Form'
import * as Form from '@radix-ui/react-form'
import { BarLoader } from 'react-spinners'
import { useOrg } from '@components/Contexts/OrgContext'

View file

@ -5,7 +5,7 @@ import FormLayout, {
FormLabel,
FormMessage,
Input,
} from '@components/StyledElements/Form/Form'
} from '@components/Objects/StyledElements/Form/Form'
import React, { useState } from 'react'
import * as Form from '@radix-ui/react-form'
import BarLoader from 'react-spinners/BarLoader'

View file

@ -6,7 +6,7 @@ import FormLayout, {
FormMessage,
Input,
Textarea,
} from '@components/StyledElements/Form/Form'
} from '@components/Objects/StyledElements/Form/Form'
import React, { useState } from 'react'
import * as Form from '@radix-ui/react-form'
import BarLoader from 'react-spinners/BarLoader'

View file

@ -5,7 +5,7 @@ import FormLayout, {
FormLabel,
FormMessage,
Input,
} from '@components/StyledElements/Form/Form'
} from '@components/Objects/StyledElements/Form/Form'
import React, { useState } from 'react'
import * as Form from '@radix-ui/react-form'
import BarLoader from 'react-spinners/BarLoader'

View file

@ -5,7 +5,7 @@ import FormLayout, {
Textarea,
FormLabel,
ButtonBlack,
} from '@components/StyledElements/Form/Form'
} from '@components/Objects/StyledElements/Form/Form'
import { FormMessage } from '@radix-ui/react-form'
import * as Form from '@radix-ui/react-form'
import React, { useState } from 'react'

View file

@ -1,12 +1,11 @@
'use client'
import { Input } from "@/components/ui/input"
import { Textarea } from "@/components/ui/textarea"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { Label } from "@/components/ui/label"
import { Input } from "@components/Ui/input"
import { Textarea } from "@components/Ui/textarea"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@components/Ui/select"
import FormLayout, {
FormField,
FormLabelAndMessage,
} from '@components/StyledElements/Form/Form'
} from '@components/Objects/StyledElements/Form/Form'
import * as Form from '@radix-ui/react-form'
import { createNewCourse } from '@services/courses/courses'
import { getOrganizationContextInfoWithoutCredentials } from '@services/organizations/orgs'
@ -18,9 +17,8 @@ import { useLHSession } from '@components/Contexts/LHSessionContext'
import toast from 'react-hot-toast'
import { useFormik } from 'formik'
import * as Yup from 'yup'
import { getCourseThumbnailMediaDirectory } from '@services/media/media'
import { ArrowBigUpDash, UploadCloud, Image as ImageIcon } from 'lucide-react'
import UnsplashImagePicker from "@components/Dashboard/Course/EditCourseGeneral/UnsplashImagePicker"
import { UploadCloud, Image as ImageIcon } from 'lucide-react'
import UnsplashImagePicker from "@components/Dashboard/Pages/Course/EditCourseGeneral/UnsplashImagePicker"
const validationSchema = Yup.object().shape({
name: Yup.string()

View file

@ -3,7 +3,7 @@ import FormLayout, {
FormField,
FormLabelAndMessage,
Input,
} from '@components/StyledElements/Form/Form'
} from '@components/Objects/StyledElements/Form/Form'
import * as Form from '@radix-ui/react-form'
import { useOrg } from '@components/Contexts/OrgContext'
import React from 'react'

View file

@ -3,7 +3,7 @@ import FormLayout, {
FormField,
FormLabelAndMessage,
Input,
} from '@components/StyledElements/Form/Form'
} from '@components/Objects/StyledElements/Form/Form'
import * as Form from '@radix-ui/react-form'
import { useOrg } from '@components/Contexts/OrgContext'
import React from 'react'

View file

@ -6,7 +6,7 @@ import FormLayout, {
Flex,
FormField,
FormLabel,
} from '@components/StyledElements/Form/Form'
} from '@components/Objects/StyledElements/Form/Form'
import * as Form from '@radix-ui/react-form'
import { FormMessage } from '@radix-ui/react-form'
import { getAPIUrl } from '@services/config/config'

View file

@ -0,0 +1,277 @@
import Modal from '@components/Objects/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 } 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: <Book size={16} />,
},
],
},
{
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: <Info size={16} />,
},
],
},
{
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: <Info size={16} />,
},
],
},
{
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: <Sparkle size={16} />,
},
],
},
{
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: <SquareUser size={16} />,
},
],
},
{
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: <Globe size={16} />,
},
],
},
];
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 (
<div>
{isUserAdmin.isAdmin && !isUserAdmin.loading && !isOnboardingComplete && <Modal
isDialogOpen={isModalOpen}
onOpenChange={setIsModalOpen}
minHeight="sm"
minWidth='md'
dialogContent={
<OnboardingScreen
step={onboardingData[currentStep]}
onboardingData={onboardingData}
currentStep={currentStep}
nextStep={nextStep}
skipOnboarding={skipOnboarding}
setIsModalOpen={setIsModalOpen}
goToStep={goToStep}
/>
}
dialogTrigger={
<div className='fixed pb-10 w-full bottom-0 bg-gradient-to-t from-1% from-gray-950/25 to-transparent'>
<div className='bg-gray-950 flex space-x-2 font-bold cursor-pointer hover:bg-gray-900 shadow-md items-center text-gray-200 px-5 py-2 w-fit rounded-full mx-auto'>
<Sprout size={20} />
<p>Onboarding</p>
<div className='h-2 w-2 bg-green-500 animate-pulse rounded-full'></div>
</div>
</div>
}
/>}
</div>
);
};
interface OnboardingScreenProps {
step: OnboardingStep;
currentStep: number;
nextStep: () => void;
skipOnboarding: () => void;
goToStep: (index: number) => void;
setIsModalOpen: (value: boolean) => void;
onboardingData: OnboardingStep[];
}
const OnboardingScreen: React.FC<OnboardingScreenProps> = ({
step,
currentStep,
nextStep,
skipOnboarding,
goToStep,
onboardingData,
setIsModalOpen,
}) => {
const isLastStep = currentStep === onboardingData.length - 1;
return (
<div className='flex flex-col'>
<div className='onboarding_screens flex-col px-4 py-4'>
<div className='flex-grow rounded-xl'>
<Image unoptimized className='mx-auto shadow-md shadow-gray-200 rounded-lg aspect-auto' alt='' priority quality={100} src={step.imageSrc} />
</div>
<div className='grid grid-flow-col justify-stretch space-x-3 mt-4'>
{onboardingData.map((_, index) => (
<div
key={index}
onClick={() => goToStep(index)}
className={`h-[7px] w-auto ${index === currentStep ? 'bg-black' : 'bg-gray-300'} hover:bg-gray-700 rounded-lg shadow-md cursor-pointer`}
></div>
))}
</div>
</div>
<div className='onboarding_text flex flex-col h-[90px] py-2 px-4 leading-tight'>
<h2 className='text-xl font-bold'>{step.title}</h2>
<p className='text-md font-normal'>{step.description}</p>
</div>
<div className='onboarding_actions flex flex-row-reverse w-full px-4'>
<div className='flex flex-row justify-between w-full py-2'>
<div className='utils_buttons flex flex-row space-x-2'>
<div
className="inline-flex items-center px-5 space-x-1 cursor-pointer py-1 rounded-full text-gray-600 antialiased font-bold bg-gray-100 hover:bg-gray-200"
onClick={() => setIsModalOpen(false)}
>
<PictureInPicture size={16} />
</div>
</div>
<div className='actions_buttons flex space-x-2'>
{step.buttons?.map((button, index) => (
<div
key={index}
className="inline-flex items-center px-5 space-x-2 cursor-pointer py-1 rounded-full text-gray-200 antialiased font-bold bg-black hover:bg-gray-700 shadow-md whitespace-nowrap"
onClick={button.action}
>
<p>{button.label}</p>
{button.icon}
</div>
))}
{isLastStep ? (
<div
className="inline-flex items-center px-5 space-x-2 cursor-pointer py-1 rounded-full text-gray-200 antialiased font-bold bg-black hover:bg-gray-700 shadow-md whitespace-nowrap"
onClick={nextStep}
>
<p>Finish Onboarding</p>
<Check size={16} />
</div>
) : (
<div
className="inline-flex items-center px-5 space-x-2 cursor-pointer py-1 rounded-full text-gray-200 antialiased font-bold bg-black hover:bg-gray-700 shadow-md whitespace-nowrap"
onClick={nextStep}
>
<p>Next</p>
<ArrowRight size={16} />
</div>
)}
</div>
</div>
</div>
</div>
);
};
export default Onboarding;

View file

@ -0,0 +1,12 @@
'use client'
function NewCollectionButton() {
return (
<button className="rounded-lg bg-black hover:scale-105 transition-all duration-100 ease-linear antialiased ring-offset-purple-800 p-2 px-5 my-auto font text-xs font-bold text-white drop-shadow-lg flex space-x-2 items-center">
<div>New Collection </div>
<div className="text-md bg-neutral-800 px-1 rounded-full">+</div>
</button>
)
}
export default NewCollectionButton

View file

@ -0,0 +1,12 @@
'use client'
function NewCourseButton() {
return (
<button className="rounded-lg bg-black hover:scale-105 transition-all duration-100 ease-linear antialiased ring-offset-purple-800 p-2 px-5 my-auto font text-xs font-bold text-white drop-shadow-lg flex space-x-2 items-center">
<div>New Course </div>
<div className="text-md bg-neutral-800 px-1 rounded-full">+</div>
</button>
)
}
export default NewCourseButton

View file

@ -0,0 +1,143 @@
'use client'
import React from 'react'
import * as Dialog from '@radix-ui/react-dialog'
import { styled, keyframes } from '@stitches/react'
import { blackA } from '@radix-ui/colors'
import { AlertTriangle, Info } from 'lucide-react'
type ModalParams = {
confirmationMessage: string
confirmationButtonText: string
dialogTitle: string
functionToExecute: any
dialogTrigger?: React.ReactNode
status?: 'warning' | 'info'
buttonid?: string
}
const ConfirmationModal = (params: ModalParams) => {
const [isDialogOpen, setIsDialogOpen] = React.useState(false)
const warningColors = 'bg-red-100 text-red-600'
const infoColors = 'bg-blue-100 text-blue-600'
const warningButtonColors = 'text-white bg-red-500 hover:bg-red-600'
const infoButtonColors = 'text-white bg-blue-500 hover:bg-blue-600'
const onOpenChange = React.useCallback(
(open: any) => {
setIsDialogOpen(open)
},
[setIsDialogOpen]
)
return (
<Dialog.Root open={isDialogOpen} onOpenChange={onOpenChange}>
{params.dialogTrigger ? (
<Dialog.Trigger asChild>{params.dialogTrigger}</Dialog.Trigger>
) : null}
<Dialog.Portal>
<DialogOverlay />
<DialogContent>
<div className="h-26 flex space-x-4 tracking-tight">
<div
className={`icon p-6 rounded-xl flex items-center align-content-center ${
params.status === 'warning' ? warningColors : infoColors
}`}
>
{params.status === 'warning' ? (
<AlertTriangle size={35} />
) : (
<Info size={35} />
)}
</div>
<div className="text pt-1 space-x-0 w-auto flex-grow">
<div className="text-xl font-bold text-black ">
{params.dialogTitle}
</div>
<div className="text-md text-gray-500 w-60 leading-tight">
{params.confirmationMessage}
</div>
<div className="flex flex-row-reverse pt-2">
<div
id={params.buttonid}
className={`rounded-md text-sm px-3 py-2 font-bold flex justify-center items-center hover:cursor-pointer ${
params.status === 'warning'
? warningButtonColors
: infoButtonColors
}
hover:shadow-lg transition duration-300 ease-in-out
`}
onClick={() => {
params.functionToExecute()
setIsDialogOpen(false)
}}
>
{params.confirmationButtonText}
</div>
</div>
</div>
</div>
</DialogContent>
</Dialog.Portal>
</Dialog.Root>
)
}
const overlayShow = keyframes({
'0%': { opacity: 0 },
'100%': { opacity: 1 },
})
const overlayClose = keyframes({
'0%': { opacity: 1 },
'100%': { opacity: 0 },
})
const contentShow = keyframes({
'0%': { opacity: 0, transform: 'translate(-50%, -50%) scale(.96)' },
'100%': { opacity: 1, transform: 'translate(-50%, -50%) scale(1)' },
})
const contentClose = keyframes({
'0%': { opacity: 1, transform: 'translate(-50%, -50%) scale(1)' },
'100%': { opacity: 0, transform: 'translate(-50%, -52%) scale(.96)' },
})
const DialogOverlay = styled(Dialog.Overlay, {
backgroundColor: blackA.blackA9,
position: 'fixed',
zIndex: 500,
inset: 0,
animation: `${overlayShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`,
'&[data-state="closed"]': {
animation: `${overlayClose} 150ms cubic-bezier(0.16, 1, 0.3, 1)`,
},
})
const DialogContent = styled(Dialog.Content, {
backgroundColor: 'white',
borderRadius: 18,
zIndex: 501,
boxShadow:
'hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px',
position: 'fixed',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 'auto',
minWidth: '500px',
overflow: 'hidden',
height: 'auto',
maxHeight: '85vh',
maxWidth: '600px',
padding: 11,
animation: `${contentShow} 150ms cubic-bezier(0.16, 1, 0.3, 1)`,
'&:focus': { outline: 'none' },
'&[data-state="closed"]': {
animation: `${contentClose} 150ms cubic-bezier(0.16, 1, 0.3, 1)`,
},
transition: 'max-height 0.3s ease-out',
})
export default ConfirmationModal

View file

@ -0,0 +1,45 @@
'use client'
import { getUriWithoutOrg } from '@services/config/config'
import { AlertTriangle, HomeIcon, RefreshCcw } from 'lucide-react'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import React from 'react'
function ErrorUI(params: { message?: string, submessage?: string }) {
const router = useRouter()
function reloadPage() {
router.refresh()
window.location.reload()
}
return (
<div className="flex flex-col py-10 mx-auto antialiased items-center space-y-6 bg-gradient-to-b from-rose-100 to-rose-100/5 ">
<div className="flex flex-row items-center space-x-5 rounded-xl ">
<AlertTriangle className="text-rose-700" size={45} />
<div className='flex flex-col'>
<p className="text-3xl font-bold text-rose-700">{params.message ? params.message : 'Something went wrong'}</p>
<p className="text-lg font-bold text-rose-700">{params.submessage ? params.submessage : ''}</p>
</div>
</div>
<div className='flex space-x-4'>
<button
onClick={() => reloadPage()}
className="flex space-x-2 items-center rounded-full px-4 py-1 text-rose-200 bg-rose-700 hover:bg-rose-800 transition-all ease-linear shadow-lg "
>
<RefreshCcw className="text-rose-200" size={17} />
<span className="text-md font-bold">Retry</span>
</button>
<Link
href={getUriWithoutOrg('/home')}
className="flex space-x-2 items-center rounded-full px-4 py-1 text-gray-200 bg-gray-700 hover:bg-gray-800 transition-all ease-linear shadow-lg "
>
<HomeIcon className="text-gray-200" size={17} />
<span className="text-md font-bold">Home</span>
</Link>
</div>
</div>
)
}
export default ErrorUI

View file

@ -0,0 +1,109 @@
import React from 'react'
import * as Form from '@radix-ui/react-form'
import { styled } from '@stitches/react'
import { blackA } from '@radix-ui/colors'
import { Info } from 'lucide-react'
const FormLayout = (props: any, onSubmit: any) => (
<FormRoot className="h-fit" onSubmit={props.onSubmit}>
{props.children}
</FormRoot>
)
export const FormLabelAndMessage = (props: {
label: string
message?: string
}) => (
<div className="flex items-center space-x-3">
<FormLabel className="grow text-sm">{props.label}</FormLabel>
{(props.message && (
<div className="text-red-700 text-sm items-center rounded-md flex space-x-1">
<Info size={10} />
<div>{props.message}</div>
</div>
)) || <></>}
</div>
)
export const FormRoot = styled(Form.Root, {
margin: 7,
})
export const FormField = styled(Form.Field, {
display: 'grid',
marginBottom: 10,
})
export const FormLabel = styled(Form.Label, {
fontWeight: 500,
lineHeight: '35px',
color: 'black',
})
export const FormMessage = styled(Form.Message, {
fontSize: 13,
color: 'white',
opacity: 0.8,
})
export const Flex = styled('div', { display: 'flex' })
export const inputStyles = {
all: 'unset',
boxSizing: 'border-box',
width: '100%',
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: 4,
fontSize: 15,
color: '#7c7c7c',
background: '#fbfdff',
boxShadow: `0 0 0 1px #edeeef`,
'&:hover': { boxShadow: `0 0 0 1px #edeeef` },
'&:focus': { boxShadow: `0 0 0 2px #edeeef` },
'&::selection': { backgroundColor: blackA.blackA9, color: 'white' },
}
export const Input = styled('input', {
...inputStyles,
height: 35,
lineHeight: 1,
padding: '0 10px',
border: 'none',
})
export const Textarea = styled('textarea', {
...inputStyles,
resize: 'none',
padding: 10,
})
export const ButtonBlack = styled('button', {
variants: {
state: {
loading: {
pointerEvents: 'none',
backgroundColor: '#808080',
},
none: {},
},
},
all: 'unset',
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: 8,
padding: '0 15px',
fontSize: 15,
lineHeight: 1,
fontWeight: 500,
height: 35,
background: '#000000',
color: '#FFFFFF',
'&:hover': { backgroundColor: '#181818', cursor: 'pointer' },
'&:focus': { boxShadow: `0 0 0 2px black` },
})
export default FormLayout

View file

@ -0,0 +1,37 @@
'use client'
import { getUriWithoutOrg } from '@services/config/config'
import { Diamond, Home, PersonStanding } from 'lucide-react'
import Link from 'next/link'
import React from 'react'
function InfoUI(params: { message?: string, submessage?: string, cta?: string, href: string }) {
return (
<div className="flex flex-col py-10 mx-auto antialiased items-center space-y-6 bg-gradient-to-b from-yellow-100 to-yellow-100/5 ">
<div className="flex flex-row items-center space-x-5 rounded-xl ">
<Diamond className="text-yellow-700" size={45} />
<div className='flex flex-col'>
<p className="text-3xl font-bold text-yellow-700">{params.message ? params.message : 'Something went wrong'}</p>
<p className="text-lg font-bold text-yellow-700">{params.submessage ? params.submessage : ''}</p>
</div>
</div>
{params.cta && <div className='flex space-x-4'>
<Link
href={params.href}
className="flex space-x-2 items-center rounded-full px-4 py-1 text-yellow-200 bg-yellow-700 hover:bg-yellow-800 transition-all ease-linear shadow-lg "
>
<PersonStanding className="text-yellow-200" size={17} />
<span className="text-md font-bold">{params.cta}</span>
</Link>
<Link
href={getUriWithoutOrg('/home')}
className="flex space-x-2 items-center rounded-full px-4 py-1 text-gray-200 bg-gray-700 hover:bg-gray-800 transition-all ease-linear shadow-lg "
>
<Home className="text-gray-200" size={17} />
<span className="text-md font-bold">Home</span>
</Link>
</div>}
</div>
)
}
export default InfoUI

View file

@ -0,0 +1,80 @@
'use client'
import React from 'react'
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger, DialogFooter } from "@components/Ui/dialog"
import { ButtonBlack } from '../Form/Form'
import { cn } from "@/lib/utils"
type ModalParams = {
dialogTitle?: string
dialogDescription?: string
dialogContent: React.ReactNode
dialogClose?: React.ReactNode | null
dialogTrigger?: React.ReactNode
addDefCloseButton?: boolean
onOpenChange: (open: boolean) => void
isDialogOpen?: boolean
minHeight?: 'sm' | 'md' | 'lg' | 'xl' | 'no-min'
minWidth?: 'sm' | 'md' | 'lg' | 'xl' | 'no-min'
customHeight?: string
customWidth?: string
}
const Modal = (params: ModalParams) => {
const getMinHeight = () => {
switch (params.minHeight) {
case 'sm': return 'min-h-[300px]'
case 'md': return 'min-h-[500px]'
case 'lg': return 'min-h-[700px]'
case 'xl': return 'min-h-[900px]'
default: return ''
}
}
const getMinWidth = () => {
switch (params.minWidth) {
case 'sm': return 'min-w-[600px]'
case 'md': return 'min-w-[800px]'
case 'lg': return 'min-w-[1000px]'
case 'xl': return 'min-w-[1200px]'
default: return ''
}
}
return (
<Dialog open={params.isDialogOpen} onOpenChange={params.onOpenChange}>
{params.dialogTrigger && (
<DialogTrigger asChild>{params.dialogTrigger}</DialogTrigger>
)}
<DialogContent className={cn(
"overflow-auto",
getMinHeight(),
getMinWidth(),
params.customHeight,
params.customWidth
)}>
{params.dialogTitle && params.dialogDescription && (
<DialogHeader className="text-center flex flex-col space-y-0.5 w-full">
<DialogTitle>{params.dialogTitle}</DialogTitle>
<DialogDescription>{params.dialogDescription}</DialogDescription>
</DialogHeader>
)}
<div className="overflow-auto">
{params.dialogContent}
</div>
{(params.dialogClose || params.addDefCloseButton) && (
<DialogFooter>
{params.dialogClose}
{params.addDefCloseButton && (
<ButtonBlack type="submit">
Close
</ButtonBlack>
)}
</DialogFooter>
)}
</DialogContent>
</Dialog>
)
}
export default Modal

View file

@ -0,0 +1,27 @@
import Image from 'next/image'
import CoursesLogo from 'public/svg/courses.svg'
import CollectionsLogo from 'public/svg/collections.svg'
import TrailLogo from 'public/svg/trail.svg'
function TypeOfContentTitle(props: { title: string; type: string }) {
function getLogo() {
if (props.type == 'col') {
return CollectionsLogo
} else if (props.type == 'cou') {
return CoursesLogo
} else if (props.type == 'tra') {
return TrailLogo
}
}
return (
<div className="home_category_title flex my-5 items-center">
<div className="ml-2 rounded-full ring-1 ring-slate-900/5 shadow-inner p-2 my-auto mr-4">
<Image unoptimized className="" src={getLogo()} alt="Courses logo" />
</div>
<h1 className="font-bold text-2xl">{props.title}</h1>
</div>
)
}
export default TypeOfContentTitle

View file

@ -0,0 +1,13 @@
'use client'
import React from 'react'
import { Toaster } from 'react-hot-toast'
function Toast() {
return (
<>
<Toaster />
</>
)
}
export default Toast

View file

@ -0,0 +1,97 @@
'use client'
import React from 'react'
import * as Tooltip from '@radix-ui/react-tooltip'
import { styled, keyframes } from '@stitches/react'
type TooltipProps = {
sideOffset?: number
content: React.ReactNode
children: React.ReactNode
side?: 'top' | 'right' | 'bottom' | 'left' // default is bottom
slateBlack?: boolean
}
const ToolTip = (props: TooltipProps) => {
return (
<Tooltip.Provider delayDuration={200}>
<Tooltip.Root>
<Tooltip.Trigger asChild>{props.children}</Tooltip.Trigger>
<Tooltip.Portal>
<TooltipContent
slateBlack={props.slateBlack}
side={props.side ? props.side : 'bottom'}
sideOffset={props.sideOffset}
>
{props.content}
</TooltipContent>
</Tooltip.Portal>
</Tooltip.Root>
</Tooltip.Provider>
)
}
const slideUpAndFade = keyframes({
'0%': { opacity: 0, transform: 'translateY(2px)' },
'100%': { opacity: 1, transform: 'translateY(0)' },
})
const slideRightAndFade = keyframes({
'0%': { opacity: 0, transform: 'translateX(-2px)' },
'100%': { opacity: 1, transform: 'translateX(0)' },
})
const slideDownAndFade = keyframes({
'0%': { opacity: 0, transform: 'translateY(-2px)' },
'100%': { opacity: 1, transform: 'translateY(0)' },
})
const slideLeftAndFade = keyframes({
'0%': { opacity: 0, transform: 'translateX(2px)' },
'100%': { opacity: 1, transform: 'translateX(0)' },
})
const closeAndFade = keyframes({
'0%': { opacity: 1 },
'100%': { opacity: 0 },
})
const TooltipContent = styled(Tooltip.Content, {
variants: {
slateBlack: {
true: {
backgroundColor: ' #0d0d0d',
color: 'white',
},
},
},
borderRadius: 4,
padding: '5px 10px',
fontSize: 12,
lineHeight: 1,
color: 'black',
backgroundColor: 'rgba(217, 217, 217, 0.50)',
zIndex: 500,
boxShadow:
'hsl(206 22% 7% / 35%) 0px 10px 38px -10px, hsl(206 22% 7% / 20%) 0px 10px 20px -15px',
userSelect: 'none',
animationDuration: '400ms',
animationTimingFunction: 'cubic-bezier(0.16, 1, 0.3, 1)',
willChange: 'transform, opacity',
'&[data-state="delayed-open"]': {
'&[data-side="top"]': { animationName: slideDownAndFade },
'&[data-side="right"]': { animationName: slideLeftAndFade },
'&[data-side="bottom"]': { animationName: slideUpAndFade },
'&[data-side="left"]': { animationName: slideRightAndFade },
},
// closing animation
'&[data-state="closed"]': {
'&[data-side="top"]': { animationName: closeAndFade },
'&[data-side="right"]': { animationName: closeAndFade },
'&[data-side="bottom"]': { animationName: closeAndFade },
'&[data-side="left"]': { animationName: closeAndFade },
},
})
export default ToolTip

View file

@ -0,0 +1,9 @@
function GeneralWrapperStyled({ children }: { children: React.ReactNode }) {
return (
<div className="max-w-screen-2xl mx-auto px-4 sm:px-6 lg:px-8 py-5 tracking-tight z-50">
{children}
</div>
)
}
export default GeneralWrapperStyled

View file

@ -1,7 +1,7 @@
'use client'
import { useOrg } from '@components/Contexts/OrgContext'
import AuthenticatedClientElement from '@components/Security/AuthenticatedClientElement'
import ConfirmationModal from '@components/StyledElements/ConfirmationModal/ConfirmationModal'
import ConfirmationModal from '@components/Objects/StyledElements/ConfirmationModal/ConfirmationModal'
import { getUriWithOrg } from '@services/config/config'
import { deleteCollection } from '@services/courses/collections'
import { getCourseThumbnailMediaDirectory } from '@services/media/media'

View file

@ -1,7 +1,7 @@
'use client'
import { useOrg } from '@components/Contexts/OrgContext'
import AuthenticatedClientElement from '@components/Security/AuthenticatedClientElement'
import ConfirmationModal from '@components/StyledElements/ConfirmationModal/ConfirmationModal'
import ConfirmationModal from '@components/Objects/StyledElements/ConfirmationModal/ConfirmationModal'
import { getUriWithOrg } from '@services/config/config'
import { deleteCourseFromBackend } from '@services/courses/courses'
import { getCourseThumbnailMediaDirectory } from '@services/media/media'
@ -17,7 +17,7 @@ import {
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
} from "@components/Ui/dropdown-menu"
type Course = {
course_uuid: string

View file

@ -0,0 +1,27 @@
import Image from 'next/image'
import Link from 'next/link'
import blacklogo from '@public/black_logo.png'
import React, { useEffect } from 'react'
import { useOrg } from '../Contexts/OrgContext'
function Watermark() {
const org = useOrg() as any
useEffect(() => {
}
, [org]);
if (org?.config?.config?.general?.watermark) {
return (
<div className='fixed bottom-8 right-8'>
<Link href={`https://www.learnhouse.app/?source=in-app`} className="flex items-center cursor-pointer bg-white/80 backdrop-blur-lg text-gray-700 rounded-2xl p-2 light-shadow text-xs px-5 font-semibold space-x-2">
<p>Made with</p>
<Image unoptimized src={blacklogo} alt="logo" quality={100} width={85} />
</Link>
</div>
)
}
return null
}
export default Watermark