mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
feat: add ui skeletons for course updates
This commit is contained in:
parent
d1a620b2e2
commit
f0e5fa925d
5 changed files with 252 additions and 63 deletions
|
|
@ -15,6 +15,7 @@ import {
|
||||||
import { ArrowRight, Check, File, Sparkles, Video } from 'lucide-react'
|
import { ArrowRight, Check, File, Sparkles, Video } from 'lucide-react'
|
||||||
import { useOrg } from '@components/Contexts/OrgContext'
|
import { useOrg } from '@components/Contexts/OrgContext'
|
||||||
import UserAvatar from '@components/Objects/UserAvatar'
|
import UserAvatar from '@components/Objects/UserAvatar'
|
||||||
|
import CourseUpdates from '@components/Objects/CourseUpdates/CourseUpdates'
|
||||||
|
|
||||||
const CourseClient = (props: any) => {
|
const CourseClient = (props: any) => {
|
||||||
const [user, setUser] = useState<any>({})
|
const [user, setUser] = useState<any>({})
|
||||||
|
|
@ -68,9 +69,14 @@ const CourseClient = (props: any) => {
|
||||||
<PageLoading></PageLoading>
|
<PageLoading></PageLoading>
|
||||||
) : (
|
) : (
|
||||||
<GeneralWrapperStyled>
|
<GeneralWrapperStyled>
|
||||||
<div className="pb-3">
|
<div className="pb-3 flex justify-between items-center">
|
||||||
<p className="text-md font-bold text-gray-400 pb-2">Course</p>
|
<div>
|
||||||
<h1 className="text-3xl -mt-3 font-bold">{course.name}</h1>
|
<p className="text-md font-bold text-gray-400 pb-2">Course</p>
|
||||||
|
<h1 className="text-3xl -mt-3 font-bold">{course.name}</h1>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<CourseUpdates />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{props.course?.thumbnail_image && org ? (
|
{props.course?.thumbnail_image && org ? (
|
||||||
|
|
@ -150,13 +156,13 @@ const CourseClient = (props: any) => {
|
||||||
<div className="courseicon items-center flex space-x-2 text-neutral-400">
|
<div className="courseicon items-center flex space-x-2 text-neutral-400">
|
||||||
{activity.activity_type ===
|
{activity.activity_type ===
|
||||||
'TYPE_DYNAMIC' && (
|
'TYPE_DYNAMIC' && (
|
||||||
<div className="bg-gray-100 px-2 py-2 rounded-full">
|
<div className="bg-gray-100 px-2 py-2 rounded-full">
|
||||||
<Sparkles
|
<Sparkles
|
||||||
className="text-gray-400"
|
className="text-gray-400"
|
||||||
size={13}
|
size={13}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{activity.activity_type === 'TYPE_VIDEO' && (
|
{activity.activity_type === 'TYPE_VIDEO' && (
|
||||||
<div className="bg-gray-100 px-2 py-2 rounded-full">
|
<div className="bg-gray-100 px-2 py-2 rounded-full">
|
||||||
<Video
|
<Video
|
||||||
|
|
@ -167,13 +173,13 @@ const CourseClient = (props: any) => {
|
||||||
)}
|
)}
|
||||||
{activity.activity_type ===
|
{activity.activity_type ===
|
||||||
'TYPE_DOCUMENT' && (
|
'TYPE_DOCUMENT' && (
|
||||||
<div className="bg-gray-100 px-2 py-2 rounded-full">
|
<div className="bg-gray-100 px-2 py-2 rounded-full">
|
||||||
<File
|
<File
|
||||||
className="text-gray-400"
|
className="text-gray-400"
|
||||||
size={13}
|
size={13}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Link
|
<Link
|
||||||
className="flex font-semibold grow pl-2 text-neutral-500"
|
className="flex font-semibold grow pl-2 text-neutral-500"
|
||||||
|
|
@ -191,25 +197,25 @@ const CourseClient = (props: any) => {
|
||||||
<div className="flex ">
|
<div className="flex ">
|
||||||
{activity.activity_type ===
|
{activity.activity_type ===
|
||||||
'TYPE_DYNAMIC' && (
|
'TYPE_DYNAMIC' && (
|
||||||
<>
|
<>
|
||||||
<Link
|
<Link
|
||||||
className="flex grow pl-2 text-gray-500"
|
className="flex grow pl-2 text-gray-500"
|
||||||
href={
|
href={
|
||||||
getUriWithOrg(orgslug, '') +
|
getUriWithOrg(orgslug, '') +
|
||||||
`/course/${courseuuid}/activity/${activity.activity_uuid.replace(
|
`/course/${courseuuid}/activity/${activity.activity_uuid.replace(
|
||||||
'activity_',
|
'activity_',
|
||||||
''
|
''
|
||||||
)}`
|
)}`
|
||||||
}
|
}
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
<div className="text-xs bg-gray-100 text-gray-400 font-bold px-2 py-1 rounded-full flex space-x-1 items-center">
|
<div className="text-xs bg-gray-100 text-gray-400 font-bold px-2 py-1 rounded-full flex space-x-1 items-center">
|
||||||
<p>Page</p>
|
<p>Page</p>
|
||||||
<ArrowRight size={13} />
|
<ArrowRight size={13} />
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{activity.activity_type === 'TYPE_VIDEO' && (
|
{activity.activity_type === 'TYPE_VIDEO' && (
|
||||||
<>
|
<>
|
||||||
<Link
|
<Link
|
||||||
|
|
@ -232,25 +238,25 @@ const CourseClient = (props: any) => {
|
||||||
)}
|
)}
|
||||||
{activity.activity_type ===
|
{activity.activity_type ===
|
||||||
'TYPE_DOCUMENT' && (
|
'TYPE_DOCUMENT' && (
|
||||||
<>
|
<>
|
||||||
<Link
|
<Link
|
||||||
className="flex grow pl-2 text-gray-500"
|
className="flex grow pl-2 text-gray-500"
|
||||||
href={
|
href={
|
||||||
getUriWithOrg(orgslug, '') +
|
getUriWithOrg(orgslug, '') +
|
||||||
`/course/${courseuuid}/activity/${activity.activity_uuid.replace(
|
`/course/${courseuuid}/activity/${activity.activity_uuid.replace(
|
||||||
'activity_',
|
'activity_',
|
||||||
''
|
''
|
||||||
)}`
|
)}`
|
||||||
}
|
}
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
<div className="text-xs bg-gray-100 text-gray-400 font-bold px-2 py-1 rounded-full flex space-x-1 items-center">
|
<div className="text-xs bg-gray-100 text-gray-400 font-bold px-2 py-1 rounded-full flex space-x-1 items-center">
|
||||||
<p>Document</p>
|
<p>Document</p>
|
||||||
<ArrowRight size={13} />
|
<ArrowRight size={13} />
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
184
apps/web/components/Objects/CourseUpdates/CourseUpdates.tsx
Normal file
184
apps/web/components/Objects/CourseUpdates/CourseUpdates.tsx
Normal file
|
|
@ -0,0 +1,184 @@
|
||||||
|
import { PencilLine, Rss } from 'lucide-react'
|
||||||
|
import React from 'react'
|
||||||
|
import { motion } from 'framer-motion'
|
||||||
|
import { useFormik } from 'formik'
|
||||||
|
import * as Form from '@radix-ui/react-form'
|
||||||
|
import FormLayout, {
|
||||||
|
FormField,
|
||||||
|
FormLabelAndMessage,
|
||||||
|
Input,
|
||||||
|
Textarea,
|
||||||
|
} from '@components/StyledElements/Form/Form'
|
||||||
|
|
||||||
|
function CourseUpdates() {
|
||||||
|
const [isModelOpen, setIsModelOpen] = React.useState(false)
|
||||||
|
|
||||||
|
function handleModelOpen() {
|
||||||
|
setIsModelOpen(!isModelOpen)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if user clicks outside the model, close the model
|
||||||
|
React.useEffect(() => {
|
||||||
|
function handleClickOutside(event: any) {
|
||||||
|
if (event.target.closest('.bg-white') === null) {
|
||||||
|
setIsModelOpen(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.addEventListener('mousedown', handleClickOutside)
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('mousedown', handleClickOutside)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ position: 'relative' }} className='bg-white hover:bg-neutral-50 transition-all ease-linear nice-shadow rounded-full z-20 px-5 py-1'>
|
||||||
|
<div onClick={handleModelOpen} className='flex items-center space-x-2 font-normal hover:cursor-pointer text-gray-600'>
|
||||||
|
<div><Rss size={16} /> </div>
|
||||||
|
<div className='flex space-x-2 items-center'>
|
||||||
|
<span>Updates</span>
|
||||||
|
<span className='text-xs px-2 font-bold py-1 rounded-full bg-rose-100 text-rose-900'>5</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{isModelOpen && <motion.div
|
||||||
|
initial={{ opacity: 0, y: -10 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{
|
||||||
|
type: 'spring',
|
||||||
|
stiffness: 1300,
|
||||||
|
damping: 70,
|
||||||
|
}}
|
||||||
|
style={{ position: 'absolute', top: '130%', right: 0 }}
|
||||||
|
>
|
||||||
|
<UpdatesModel />
|
||||||
|
</motion.div>}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const UpdatesModel = () => {
|
||||||
|
return (
|
||||||
|
<div className='bg-white/95 backdrop-blur-md nice-shadow rounded-lg w-[700px] overflow-hidden'>
|
||||||
|
<div className='bg-gray-50/70 flex justify-between outline outline-1 rounded-lg outline-neutral-200/40'>
|
||||||
|
<div className='py-2 px-4 font-bold text-gray-500 flex space-x-2 items-center'>
|
||||||
|
<Rss size={16} />
|
||||||
|
<span>Updates</span>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div className='py-2 px-4 space-x-2 items-center flex cursor-pointer text-xs font-medium hover:bg-gray-200 bg-gray-100 outline outline-1 outline-neutral-200/40'>
|
||||||
|
<PencilLine size={14} />
|
||||||
|
<span>New Update</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className=''>
|
||||||
|
<NewUpdateForm />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const NewUpdateForm = () => {
|
||||||
|
|
||||||
|
const validate = (values: any) => {
|
||||||
|
const errors: any = {}
|
||||||
|
|
||||||
|
if (!values.title) {
|
||||||
|
errors.title = 'Title is required'
|
||||||
|
}
|
||||||
|
if (!values.content) {
|
||||||
|
errors.content = 'Content is required'
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors
|
||||||
|
}
|
||||||
|
const formik = useFormik({
|
||||||
|
initialValues: {
|
||||||
|
title: '',
|
||||||
|
content: ''
|
||||||
|
},
|
||||||
|
validate,
|
||||||
|
onSubmit: async (values) => { },
|
||||||
|
enableReinitialize: true,
|
||||||
|
})
|
||||||
|
return (
|
||||||
|
<div className='bg-white/95 backdrop-blur-md nice-shadow rounded-lg w-[700px] overflow-hidden flex flex-col -space-y-2'>
|
||||||
|
<div className='flex flex-col -space-y-2 px-4 pt-4'>
|
||||||
|
<div className='text-gray-500 px-3 py-0.5 rounded-full font-semibold text-xs'>Test Course </div>
|
||||||
|
<div className='text-black px-3 py-0.5 rounded-full text-lg font-bold'>Add new Course Update</div>
|
||||||
|
</div>
|
||||||
|
<div className='px-5 -py-2'>
|
||||||
|
<FormLayout onSubmit={formik.handleSubmit}>
|
||||||
|
<FormField name="title">
|
||||||
|
<FormLabelAndMessage
|
||||||
|
label="Title"
|
||||||
|
message={formik.errors.title}
|
||||||
|
/>
|
||||||
|
<Form.Control asChild>
|
||||||
|
<Input
|
||||||
|
style={{ backgroundColor: 'white' }}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
value={formik.values.title}
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</Form.Control>
|
||||||
|
</FormField>
|
||||||
|
<FormField name="content">
|
||||||
|
<FormLabelAndMessage
|
||||||
|
label="Content"
|
||||||
|
message={formik.errors.content}
|
||||||
|
/>
|
||||||
|
<Form.Control asChild>
|
||||||
|
<Textarea
|
||||||
|
style={{ backgroundColor: 'white', height: '100px'}}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
value={formik.values.content}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</Form.Control>
|
||||||
|
</FormField>
|
||||||
|
<div className='flex justify-end py-2'>
|
||||||
|
<button className='bg-black text-white px-4 py-2 rounded-md text-sm font-bold antialiased'>Add Update</button>
|
||||||
|
</div>
|
||||||
|
</FormLayout>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const UpdatesListView = () => {
|
||||||
|
return (
|
||||||
|
<div className='px-5 bg-white overflow-y-auto' style={{ maxHeight: '400px' }}>
|
||||||
|
<div className='py-2 border-b border-neutral-200'>
|
||||||
|
<div className='font-bold text-gray-500'>New Update</div>
|
||||||
|
<div className='text-gray-600'>Lorem ipsum dolor sit amet consectetur adipisicing elit. Quos, doloremque.</div>
|
||||||
|
</div>
|
||||||
|
<div className='py-2 border-b border-neutral-200'>
|
||||||
|
<div className='font-bold text-gray-500'>New Update</div>
|
||||||
|
<div className='text-gray-600'>Lorem ipsum dolor sit amet consectetur adipisicing elit. Quos, doloremque.</div>
|
||||||
|
</div>
|
||||||
|
<div className='py-2 border-b border-neutral-200'>
|
||||||
|
<div className='font-bold text-gray-500'>New Update</div>
|
||||||
|
<div className='text-gray-600'>Lorem ipsum dolor sit amet consectetur adipisicing elit. Quos, doloremque.</div>
|
||||||
|
</div>
|
||||||
|
<div className='py-2 border-b border-neutral-200'>
|
||||||
|
<div className='font-bold text-gray-500'>New Update</div>
|
||||||
|
<div className='text-gray-600'>Lorem ipsum dolor sit amet consectetur adipisicing elit. Quos, doloremque.</div>
|
||||||
|
</div>
|
||||||
|
<div className='py-2 border-b border-neutral-200'>
|
||||||
|
<div className='font-bold text-gray-500'>New Update</div>
|
||||||
|
<div className='text-gray-600'>Lorem ipsum dolor sit amet consectetur adipisicing elit. Quos, doloremque.</div>
|
||||||
|
</div>
|
||||||
|
<div className='py-2 border-b border-neutral-200'>
|
||||||
|
<div className='font-bold text-gray-500'>New Update</div>
|
||||||
|
<div className='text-gray-600'>Lorem ipsum dolor sit amet consectetur adipisicing elit. Quos, doloremque.</div>
|
||||||
|
</div>
|
||||||
|
<div className='py-2 border-b border-neutral-200'>
|
||||||
|
<div className='font-bold text-gray-500'>New Update</div>
|
||||||
|
<div className='text-gray-600'>Lorem ipsum dolor sit amet consectetur adipisicing elit. Quos, doloremque.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export default CourseUpdates
|
||||||
|
|
@ -50,6 +50,7 @@ function EditorWrapper(props: EditorWrapperProps): JSX.Element {
|
||||||
}, 10);
|
}, 10);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Store the Y document in the browser
|
// Store the Y document in the browser
|
||||||
new IndexeddbPersistence(props.activity.activity_uuid, doc)
|
new IndexeddbPersistence(props.activity.activity_uuid, doc)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,14 +23,8 @@ export const Menu = (props: any) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="backdrop-blur-lg h-[60px] blur-3xl z-10" style={{}}>
|
<div className="backdrop-blur-lg h-[60px] blur-3xl -z-10" style={{}}>
|
||||||
<div
|
|
||||||
className="h-[150px] blur-3xl z-0"
|
|
||||||
style={{
|
|
||||||
background:
|
|
||||||
'radial-gradient(1397.20% 56.18% at 75.99% 53.73%, rgba(253, 182, 207, 0.08) 0%, rgba(3, 110, 146, 0.08) 100%)',
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="backdrop-blur-lg bg-white/90 fixed flex top-0 left-0 right-0 h-[60px] ring-1 ring-inset ring-gray-500/10 items-center space-x-5 shadow-[0px_4px_16px_rgba(0,0,0,0.03)] z-50">
|
<div className="backdrop-blur-lg bg-white/90 fixed flex top-0 left-0 right-0 h-[60px] ring-1 ring-inset ring-gray-500/10 items-center space-x-5 shadow-[0px_4px_16px_rgba(0,0,0,0.03)] z-50">
|
||||||
<div className="flex items-center space-x-5 w-full max-w-screen-2xl mx-auto px-16">
|
<div className="flex items-center space-x-5 w-full max-w-screen-2xl mx-auto px-16">
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,10 @@
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
|
.nice-shadow {
|
||||||
|
@apply shadow-md shadow-gray-300/25 outline outline-1 outline-neutral-200/40
|
||||||
|
}
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue