feat: add updates functionality to app

This commit is contained in:
swve 2024-04-17 21:26:21 +02:00
parent f0e5fa925d
commit 8a2ccb2534
6 changed files with 147 additions and 53 deletions

View file

@ -1,5 +1,5 @@
import { PencilLine, Rss } from 'lucide-react'
import React from 'react'
import { PencilLine, Rss, TentTree } from 'lucide-react'
import React, { useEffect } from 'react'
import { motion } from 'framer-motion'
import { useFormik } from 'formik'
import * as Form from '@radix-ui/react-form'
@ -9,8 +9,19 @@ import FormLayout, {
Input,
Textarea,
} from '@components/StyledElements/Form/Form'
import { useCourse } from '@components/Contexts/CourseContext'
import useSWR, { mutate } from 'swr'
import { getAPIUrl } from '@services/config/config'
import { swrFetcher } from '@services/utils/ts/requests'
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'
function CourseUpdates() {
const course = useCourse() as any;
const { data: updates } = useSWR(`${getAPIUrl()}courses/${course?.courseStructure.course_uuid}/updates`, swrFetcher)
const [isModelOpen, setIsModelOpen] = React.useState(false)
function handleModelOpen() {
@ -18,17 +29,16 @@ function CourseUpdates() {
}
// if user clicks outside the model, close the model
React.useEffect(() => {
React.useLayoutEffect(() => {
function handleClickOutside(event: any) {
if (event.target.closest('.bg-white') === null) {
setIsModelOpen(false)
}
console.log(event.target.id)
if (event.target.closest('.bg-white') || event.target.id === 'delete-update-button') return;
setIsModelOpen(false);
}
document.addEventListener('mousedown', handleClickOutside)
return () => {
document.removeEventListener('mousedown', handleClickOutside)
}
}, [])
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'>
@ -36,7 +46,7 @@ function CourseUpdates() {
<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>
{updates && <span className='text-xs px-2 font-bold py-0.5 rounded-full bg-rose-100 text-rose-900'>{updates.length}</span>}
</div>
</div>
{isModelOpen && <motion.div
@ -49,13 +59,15 @@ function CourseUpdates() {
}}
style={{ position: 'absolute', top: '130%', right: 0 }}
>
<UpdatesModel />
<UpdatesSection />
</motion.div>}
</div>
)
}
const UpdatesModel = () => {
const UpdatesSection = () => {
const [selectedView, setSelectedView] = React.useState('list')
const isAdmin = useAdminStatus() as boolean;
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'>
@ -64,19 +76,24 @@ const UpdatesModel = () => {
<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'>
{isAdmin && <div
onClick={() => setSelectedView('new')}
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>
<div className=''>
<NewUpdateForm />
{selectedView === 'list' && <UpdatesListView />}
{selectedView === 'new' && <NewUpdateForm setSelectedView={setSelectedView} />}
</div>
</div>
)
}
const NewUpdateForm = () => {
const NewUpdateForm = ({ setSelectedView }: any) => {
const org = useOrg() as any;
const course = useCourse() as any;
const validate = (values: any) => {
const errors: any = {}
@ -96,9 +113,32 @@ const NewUpdateForm = () => {
content: ''
},
validate,
onSubmit: async (values) => { },
onSubmit: async (values) => {
const body = {
title: values.title,
content: values.content,
course_uuid: course.courseStructure.course_uuid,
org_id: org.id
}
const res = await createCourseUpdate(body)
if (res.status === 200) {
toast.success('Update added successfully')
setSelectedView('list')
mutate(`${getAPIUrl()}courses/${course?.courseStructure.course_uuid}/updates`)
}
else {
toast.error('Failed to add update')
}
},
enableReinitialize: true,
})
useEffect(() => {
}
, [course, org])
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'>
@ -129,7 +169,7 @@ const NewUpdateForm = () => {
/>
<Form.Control asChild>
<Textarea
style={{ backgroundColor: 'white', height: '100px'}}
style={{ backgroundColor: 'white', height: '100px' }}
onChange={formik.handleChange}
value={formik.values.content}
required
@ -137,6 +177,7 @@ const NewUpdateForm = () => {
</Form.Control>
</FormField>
<div className='flex justify-end py-2'>
<button onClick={() => setSelectedView('list')} className='text-gray-500 px-4 py-2 rounded-md text-sm font-bold antialiased'>Cancel</button>
<button className='bg-black text-white px-4 py-2 rounded-md text-sm font-bold antialiased'>Add Update</button>
</div>
</FormLayout>
@ -146,39 +187,61 @@ const NewUpdateForm = () => {
}
const UpdatesListView = () => {
const course = useCourse() as any;
const isAdmin = useAdminStatus() as boolean;
const { data: updates } = useSWR(`${getAPIUrl()}courses/${course?.courseStructure.course_uuid}/updates`, swrFetcher)
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>
{updates && updates.map((update: any) => (
<div key={update.id} className='py-2 border-b border-neutral-200'>
<div className='font-bold text-gray-500 flex space-x-2 items-center justify-between '>{update.title} {isAdmin && <DeleteUpdateButton update={update} />}</div>
<div className='text-gray-600'>{update.content}</div>
</div>
))}
{(!updates || updates.length === 0) &&
<div className='text-gray-500 text-center my-10 py-2 flex flex-col space-y-2'>
<TentTree className='mx-auto' size={40} />
<p>No updates yet</p>
</div>
}
</div>
)
}
const DeleteUpdateButton = ({ update }: any) => {
const course = useCourse() as any;
const org = useOrg() as any;
const handleDelete = async () => {
const res = await deleteCourseUpdate(course.courseStructure.course_uuid, update.courseupdate_uuid)
if (res.status === 200) {
toast.success('Update deleted successfully')
mutate(`${getAPIUrl()}courses/${course?.courseStructure.course_uuid}/updates`)
}
else {
toast.error('Failed to delete update')
}
}
return (
<ConfirmationModal
confirmationButtonText="Delete Update"
confirmationMessage="Are you sure you want to delete this update?"
dialogTitle={'Delete Update ?'}
buttonid='delete-update-button'
dialogTrigger={
<div id='delete-update-button' className='text-rose-600 text-xs bg-rose-100 rounded-full px-2 py-0.5 hover:cursor-pointer'>
Delete
</div>
}
functionToExecute={() => {
handleDelete()
}}
status="warning"
></ConfirmationModal>
)
}
export default CourseUpdates

View file

@ -12,6 +12,7 @@ type ModalParams = {
functionToExecute: any
dialogTrigger?: React.ReactNode
status?: 'warning' | 'info'
buttonid?: string
}
const ConfirmationModal = (params: ModalParams) => {
@ -58,6 +59,7 @@ const ConfirmationModal = (params: ModalParams) => {
</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