mirror of
https://github.com/AustinKelsay/plebdevs.git
synced 2025-06-06 18:31:00 +00:00
improve course display, accordians, and fixed tag usage for plebdevs resource and workshop
This commit is contained in:
parent
61a78f4b28
commit
280c0e5763
@ -116,8 +116,8 @@ export default function CourseDetails({ processedEvent, paidCourse, lessons, dec
|
|||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<h1 className='text-4xl mt-6'>{processedEvent?.title}</h1>
|
<h1 className='text-4xl mt-6'>{processedEvent?.name}</h1>
|
||||||
<p className='text-xl mt-6'>{processedEvent?.summary}</p>
|
<p className='text-xl mt-6'>{processedEvent?.description}</p>
|
||||||
<div className='flex flex-row w-full mt-6 items-center'>
|
<div className='flex flex-row w-full mt-6 items-center'>
|
||||||
<Image
|
<Image
|
||||||
alt="avatar thumbnail"
|
alt="avatar thumbnail"
|
||||||
|
@ -42,6 +42,20 @@ const CourseLesson = ({ lesson, course }) => {
|
|||||||
</div>
|
</div>
|
||||||
<h1 className='text-4xl mt-6'>{lesson?.title}</h1>
|
<h1 className='text-4xl mt-6'>{lesson?.title}</h1>
|
||||||
<p className='text-xl mt-6'>{lesson?.summary}</p>
|
<p className='text-xl mt-6'>{lesson?.summary}</p>
|
||||||
|
{lesson?.additionalLinks && lesson.additionalLinks.length > 0 && (
|
||||||
|
<div className='mt-6'>
|
||||||
|
<h3 className='text-lg font-semibold mb-2'>External links:</h3>
|
||||||
|
<ul className='list-disc list-inside'>
|
||||||
|
{lesson.additionalLinks.map((link, index) => (
|
||||||
|
<li key={index}>
|
||||||
|
<a href={link} target="_blank" rel="noopener noreferrer" className='text-blue-500 hover:underline'>
|
||||||
|
{new URL(link).hostname}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className='flex flex-row w-full mt-6 items-center'>
|
<div className='flex flex-row w-full mt-6 items-center'>
|
||||||
<Image
|
<Image
|
||||||
alt="avatar thumbnail"
|
alt="avatar thumbnail"
|
||||||
|
@ -346,9 +346,9 @@ export default function DraftCourseDetails({ processedEvent, draftId, lessons })
|
|||||||
</div>
|
</div>
|
||||||
<h1 className='text-4xl mt-6'>{processedEvent?.title}</h1>
|
<h1 className='text-4xl mt-6'>{processedEvent?.title}</h1>
|
||||||
<p className='text-xl mt-6'>{processedEvent?.summary}</p>
|
<p className='text-xl mt-6'>{processedEvent?.summary}</p>
|
||||||
{processedEvent?.price && (
|
{processedEvent?.price && processedEvent?.price !== 0 ? (
|
||||||
<p className='text-lg mt-6'>Price: {processedEvent.price} sats</p>
|
<p className='text-lg mt-6'>Price: {processedEvent.price} sats</p>
|
||||||
)}
|
) : null}
|
||||||
<div className='flex flex-row w-full mt-6 items-center'>
|
<div className='flex flex-row w-full mt-6 items-center'>
|
||||||
<Image
|
<Image
|
||||||
alt="avatar thumbnail"
|
alt="avatar thumbnail"
|
||||||
|
@ -6,14 +6,6 @@ import Image from "next/image";
|
|||||||
import { useImageProxy } from "@/hooks/useImageProxy";
|
import { useImageProxy } from "@/hooks/useImageProxy";
|
||||||
import { formatDateTime, formatUnixTimestamp } from "@/utils/time";
|
import { formatDateTime, formatUnixTimestamp } from "@/utils/time";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import axios from "axios";
|
|
||||||
import { useNDKContext } from "@/context/NDKContext";
|
|
||||||
import { nip04, nip19 } from "nostr-tools";
|
|
||||||
import { v4 as uuidv4 } from "uuid";
|
|
||||||
import { useSession } from "next-auth/react";
|
|
||||||
import { NDKEvent } from "@nostr-dev-kit/ndk";
|
|
||||||
import { useToast } from "@/hooks/useToast";
|
|
||||||
import { validateEvent } from "@/utils/nostr";
|
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
|
|
||||||
const MDDisplay = dynamic(
|
const MDDisplay = dynamic(
|
||||||
@ -25,19 +17,9 @@ const MDDisplay = dynamic(
|
|||||||
|
|
||||||
const DraftCourseLesson = ({ lesson, course }) => {
|
const DraftCourseLesson = ({ lesson, course }) => {
|
||||||
const [isPublished, setIsPublished] = useState(false);
|
const [isPublished, setIsPublished] = useState(false);
|
||||||
const [user, setUser] = useState(null);
|
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { returnImageProxy } = useImageProxy();
|
const { returnImageProxy } = useImageProxy();
|
||||||
const { ndk, addSigner } = useNDKContext();
|
|
||||||
const { data: session } = useSession();
|
|
||||||
const { showToast } = useToast();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (session) {
|
|
||||||
setUser(session.user);
|
|
||||||
}
|
|
||||||
}, [session]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (lesson?.kind) {
|
if (lesson?.kind) {
|
||||||
|
@ -1,238 +0,0 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import axios from "axios";
|
|
||||||
import { InputText } from "primereact/inputtext";
|
|
||||||
import { InputNumber } from "primereact/inputnumber";
|
|
||||||
import { InputSwitch } from "primereact/inputswitch";
|
|
||||||
import { Button } from "primereact/button";
|
|
||||||
import { Dropdown } from "primereact/dropdown";
|
|
||||||
import { ProgressSpinner } from "primereact/progressspinner";
|
|
||||||
import { useSession } from 'next-auth/react';
|
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { useToast } from "@/hooks/useToast";
|
|
||||||
import { useNDKContext } from "@/context/NDKContext";
|
|
||||||
import { useWorkshopsQuery } from "@/hooks/nostrQueries/content/useWorkshopsQuery";
|
|
||||||
import { useResourcesQuery } from "@/hooks/nostrQueries/content/useResourcesQuery";
|
|
||||||
import { useDraftsQuery } from "@/hooks/apiQueries/useDraftsQuery";
|
|
||||||
import { parseEvent } from "@/utils/nostr";
|
|
||||||
import ContentDropdownItem from "@/components/content/dropdowns/ContentDropdownItem";
|
|
||||||
import 'primeicons/primeicons.css';
|
|
||||||
|
|
||||||
// todo dealing with adding drafts as new lessons
|
|
||||||
// todo disable ability to add a free lesson to a paid course and vice versa (or just make the user remove the lesson if they want to change the price)
|
|
||||||
// todo deal with error where 2 new lessons popup when only one is added from the dropdown
|
|
||||||
// todo on edit lessons need to make sure that the user is still choosing the order those lessons appear in the course
|
|
||||||
const EditCourseForm = ({ draft }) => {
|
|
||||||
const [title, setTitle] = useState('');
|
|
||||||
const [summary, setSummary] = useState('');
|
|
||||||
const [checked, setChecked] = useState(false);
|
|
||||||
const [price, setPrice] = useState(0);
|
|
||||||
const [isPaid, setIsPaid] = useState(false);
|
|
||||||
const [coverImage, setCoverImage] = useState('');
|
|
||||||
const [selectedLessons, setSelectedLessons] = useState([]);
|
|
||||||
const [selectedLessonsLoading, setSelectedLessonsLoading] = useState(false);
|
|
||||||
const [topics, setTopics] = useState(['']);
|
|
||||||
|
|
||||||
const { ndk } = useNDKContext();
|
|
||||||
const { resources, resourcesLoading } = useResourcesQuery();
|
|
||||||
const { workshops, workshopsLoading } = useWorkshopsQuery();
|
|
||||||
const { drafts, draftsLoading } = useDraftsQuery();
|
|
||||||
const { data: session } = useSession();
|
|
||||||
const router = useRouter();
|
|
||||||
const { showToast } = useToast();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (draft) {
|
|
||||||
const fetchLessonEventFromNostr = async (eventId) => {
|
|
||||||
try {
|
|
||||||
await ndk.connect();
|
|
||||||
const fetchedEvent = await ndk.fetchEvent(eventId);
|
|
||||||
return fetchedEvent ? parseEvent(fetchedEvent) : null;
|
|
||||||
} catch (error) {
|
|
||||||
showToast('error', 'Error', `Failed to fetch lesson: ${eventId}`);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchLessons = async () => {
|
|
||||||
const fetchedLessons = await Promise.all(
|
|
||||||
draft.resources.map(lesson => fetchLessonEventFromNostr(lesson.noteId))
|
|
||||||
);
|
|
||||||
setSelectedLessons(fetchedLessons.filter(Boolean));
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchLessons();
|
|
||||||
setTitle(draft.title);
|
|
||||||
setSummary(draft.summary);
|
|
||||||
setChecked(draft.price > 0);
|
|
||||||
setPrice(draft.price || 0);
|
|
||||||
setIsPaid(draft.price > 0);
|
|
||||||
setCoverImage(draft.image);
|
|
||||||
setTopics(draft.topics || ['']);
|
|
||||||
}
|
|
||||||
}, [draft, ndk, showToast, parseEvent]);
|
|
||||||
|
|
||||||
const handlePriceChange = (value) => {
|
|
||||||
setPrice(value);
|
|
||||||
setIsPaid(value > 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmit = async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Ensure selectedLessons is an array
|
|
||||||
const lessonsToUpdate = Array.isArray(selectedLessons) ? selectedLessons : [];
|
|
||||||
|
|
||||||
// Update newly added lessons with courseDraftId
|
|
||||||
const updatePromises = lessonsToUpdate
|
|
||||||
.filter(lesson => lesson && lesson.id && !draft.resources.some(r => r.id === lesson.id))
|
|
||||||
.map(lesson =>
|
|
||||||
axios.put(`/api/resources/${lesson.d}`, { courseDraftId: draft.id })
|
|
||||||
);
|
|
||||||
|
|
||||||
await Promise.all(updatePromises);
|
|
||||||
|
|
||||||
// Prepare payload for course draft update
|
|
||||||
const payload = {
|
|
||||||
id: draft.id, // Include the id in the payload
|
|
||||||
title,
|
|
||||||
summary,
|
|
||||||
image: coverImage,
|
|
||||||
price: isPaid ? price : 0,
|
|
||||||
topics,
|
|
||||||
resourceIds: lessonsToUpdate.filter(lesson => lesson && lesson.id).map(lesson => lesson.id)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Update course draft
|
|
||||||
const response = await axios.put(`/api/courses/drafts/${draft.id}`, payload);
|
|
||||||
console.log('Update response:', response.data);
|
|
||||||
|
|
||||||
showToast('success', 'Success', 'Course draft updated successfully');
|
|
||||||
router.push(`/course/${draft.id}/draft`);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error updating course draft:', error);
|
|
||||||
showToast('error', 'Failed to update course draft', error.response?.data?.details || error.message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleLessonSelect = (content) => {
|
|
||||||
if (!selectedLessons.some(lesson => lesson.id === content.id)) {
|
|
||||||
setSelectedLessons(prevLessons => [...prevLessons, content]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const removeLesson = (index) => {
|
|
||||||
const updatedSelectedLessons = selectedLessons.filter((_, i) => i !== index);
|
|
||||||
setSelectedLessons(updatedSelectedLessons);
|
|
||||||
};
|
|
||||||
|
|
||||||
const addTopic = () => {
|
|
||||||
setTopics([...topics, '']);
|
|
||||||
};
|
|
||||||
|
|
||||||
const removeTopic = (index) => {
|
|
||||||
const updatedTopics = topics.filter((_, i) => i !== index);
|
|
||||||
setTopics(updatedTopics);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleTopicChange = (index, value) => {
|
|
||||||
const updatedTopics = topics.map((topic, i) => i === index ? value : topic);
|
|
||||||
setTopics(updatedTopics);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getContentOptions = () => {
|
|
||||||
if (resourcesLoading || !resources || workshopsLoading || !workshops || draftsLoading || !drafts) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const filterContent = (content) => {
|
|
||||||
const contentPrice = content.price || 0;
|
|
||||||
return isPaid ? contentPrice > 0 : contentPrice === 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
const resourceOptions = resources.filter(filterContent).map(resource => {
|
|
||||||
const parsedResource = parseEvent(resource);
|
|
||||||
return {
|
|
||||||
label: <ContentDropdownItem content={parsedResource} onSelect={handleLessonSelect} selected={selectedLessons.some(lesson => lesson.id === parsedResource.id)} />,
|
|
||||||
value: parsedResource
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const workshopOptions = workshops.filter(filterContent).map(workshop => {
|
|
||||||
const parsedWorkshop = parseEvent(workshop);
|
|
||||||
return {
|
|
||||||
label: <ContentDropdownItem content={parsedWorkshop} onSelect={handleLessonSelect} selected={selectedLessons.some(lesson => lesson.id === parsedWorkshop.id)} />,
|
|
||||||
value: parsedWorkshop
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return [
|
|
||||||
{ label: 'Resources', items: resourceOptions },
|
|
||||||
{ label: 'Workshops', items: workshopOptions }
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
if (resourcesLoading || workshopsLoading || draftsLoading || selectedLessonsLoading) {
|
|
||||||
return <ProgressSpinner />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<form onSubmit={handleSubmit}>
|
|
||||||
<div className="p-inputgroup flex-1">
|
|
||||||
<InputText value={title} onChange={(e) => setTitle(e.target.value)} placeholder="Title" />
|
|
||||||
</div>
|
|
||||||
<div className="p-inputgroup flex-1 mt-4">
|
|
||||||
<InputText value={summary} onChange={(e) => setSummary(e.target.value)} placeholder="Summary" />
|
|
||||||
</div>
|
|
||||||
<div className="p-inputgroup flex-1 mt-4">
|
|
||||||
<InputText value={coverImage} onChange={(e) => setCoverImage(e.target.value)} placeholder="Cover Image URL" />
|
|
||||||
</div>
|
|
||||||
<div className="p-inputgroup flex-1 mt-4 flex-col">
|
|
||||||
<p className="py-2">Paid Course</p>
|
|
||||||
<InputSwitch checked={checked} onChange={(e) => setChecked(e.value)} />
|
|
||||||
{checked && (
|
|
||||||
<div className="p-inputgroup flex-1 py-4">
|
|
||||||
<InputNumber value={price} onValueChange={(e) => handlePriceChange(e.value)} placeholder="Price (sats)" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="mt-8 flex-col w-full">
|
|
||||||
<div className="mt-4 flex-col w-full">
|
|
||||||
{selectedLessons.map((lesson, index) => (
|
|
||||||
<div key={index} className="p-inputgroup flex-1 mt-4">
|
|
||||||
<ContentDropdownItem content={lesson} selected={true} />
|
|
||||||
<Button icon="pi pi-times" className="p-button-danger" onClick={() => removeLesson(index)} />
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
<div className="p-inputgroup flex-1 mt-4">
|
|
||||||
<Dropdown
|
|
||||||
options={getContentOptions()}
|
|
||||||
onChange={(e) => handleLessonSelect(e.value)}
|
|
||||||
placeholder="Add a Lesson"
|
|
||||||
optionLabel="label"
|
|
||||||
optionGroupLabel="label"
|
|
||||||
optionGroupChildren="items"
|
|
||||||
value={null}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mt-4 flex-col w-full">
|
|
||||||
{topics.map((topic, index) => (
|
|
||||||
<div key={index} className="p-inputgroup flex-1 mt-4">
|
|
||||||
<InputText value={topic} onChange={(e) => handleTopicChange(index, e.target.value)} placeholder={`Topic #${index + 1}`} className="w-full" />
|
|
||||||
{index > 0 && (
|
|
||||||
<Button icon="pi pi-times" className="p-button-danger mt-2" onClick={() => removeTopic(index)} />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
<Button type="button" icon="pi pi-plus" onClick={addTopic} className="p-button-outlined mt-2" />
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-center mt-8">
|
|
||||||
<Button type="submit" label="Update Draft" className="p-button-raised p-button-success" />
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default EditCourseForm;
|
|
@ -102,7 +102,7 @@ const ResourceForm = ({ draft = null, isPublished = false }) => {
|
|||||||
content,
|
content,
|
||||||
d: draft.d,
|
d: draft.d,
|
||||||
image: coverImage,
|
image: coverImage,
|
||||||
topics: [...topics.map(topic => topic.trim().toLowerCase()), 'plebdevs', 'resource'],
|
topics: [...new Set([...topics.map(topic => topic.trim().toLowerCase()), 'resource'])],
|
||||||
additionalLinks: additionalLinks.filter(link => link.trim() !== '')
|
additionalLinks: additionalLinks.filter(link => link.trim() !== '')
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,7 +153,7 @@ const ResourceForm = ({ draft = null, isPublished = false }) => {
|
|||||||
price: isPaidResource ? price : null,
|
price: isPaidResource ? price : null,
|
||||||
content,
|
content,
|
||||||
image: coverImage,
|
image: coverImage,
|
||||||
topics: [...topics.map(topic => topic.trim().toLowerCase()), 'plebdevs', 'resource'],
|
topics: [...new Set([...topics.map(topic => topic.trim().toLowerCase()), 'resource'])],
|
||||||
additionalLinks: additionalLinks.filter(link => link.trim() !== '')
|
additionalLinks: additionalLinks.filter(link => link.trim() !== '')
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -76,7 +76,7 @@ const WorkshopForm = ({ draft = null }) => {
|
|||||||
content: embedCode,
|
content: embedCode,
|
||||||
image: coverImage,
|
image: coverImage,
|
||||||
user: userResponse.data.id,
|
user: userResponse.data.id,
|
||||||
topics: [...topics.map(topic => topic.trim().toLowerCase()), 'plebdevs', 'workshop'],
|
topics: [...new Set([...topics.map(topic => topic.trim().toLowerCase()), 'workshop'])],
|
||||||
additionalLinks: additionalLinks.filter(link => link.trim() !== ''),
|
additionalLinks: additionalLinks.filter(link => link.trim() !== ''),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -13,12 +13,17 @@ const LessonSelector = ({ isPaidCourse, lessons, setLessons, allContent }) => {
|
|||||||
const [showResourceForm, setShowResourceForm] = useState(false);
|
const [showResourceForm, setShowResourceForm] = useState(false);
|
||||||
const [showWorkshopForm, setShowWorkshopForm] = useState(false);
|
const [showWorkshopForm, setShowWorkshopForm] = useState(false);
|
||||||
const [contentOptions, setContentOptions] = useState([]);
|
const [contentOptions, setContentOptions] = useState([]);
|
||||||
|
const [openTabs, setOpenTabs] = useState([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
updateContentOptions();
|
updateContentOptions();
|
||||||
console.log("lessons", lessons);
|
console.log("lessons", lessons);
|
||||||
}, [allContent, isPaidCourse, lessons]);
|
}, [allContent, isPaidCourse, lessons]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setOpenTabs(lessons.map((_, index) => index));
|
||||||
|
}, [lessons]);
|
||||||
|
|
||||||
const updateContentOptions = () => {
|
const updateContentOptions = () => {
|
||||||
if (!allContent || allContent.length === 0) {
|
if (!allContent || allContent.length === 0) {
|
||||||
setContentOptions([]);
|
setContentOptions([]);
|
||||||
@ -26,7 +31,7 @@ const LessonSelector = ({ isPaidCourse, lessons, setLessons, allContent }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const filterContent = (content) => {
|
const filterContent = (content) => {
|
||||||
const contentPrice = content.price || content.tags.find(tag => tag[0] === 'price')?.[1] || 0;
|
const contentPrice = content?.price || (content?.tags && content?.tags.find(tag => tag[0] === 'price')?.[1]) || 0;
|
||||||
return isPaidCourse ? contentPrice > 0 : true;
|
return isPaidCourse ? contentPrice > 0 : true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -119,6 +124,10 @@ const LessonSelector = ({ isPaidCourse, lessons, setLessons, allContent }) => {
|
|||||||
setShowWorkshopForm(false);
|
setShowWorkshopForm(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleTabChange = (e) => {
|
||||||
|
setOpenTabs(e.index);
|
||||||
|
};
|
||||||
|
|
||||||
const AccordianHeader = ({lesson, index}) => {
|
const AccordianHeader = ({lesson, index}) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
@ -131,7 +140,7 @@ const LessonSelector = ({ isPaidCourse, lessons, setLessons, allContent }) => {
|
|||||||
return (
|
return (
|
||||||
<div className="mt-8">
|
<div className="mt-8">
|
||||||
<h3>Lessons</h3>
|
<h3>Lessons</h3>
|
||||||
<Accordion multiple>
|
<Accordion multiple activeIndex={openTabs} onTabChange={handleTabChange}>
|
||||||
{lessons.map((lesson, index) => (
|
{lessons.map((lesson, index) => (
|
||||||
<AccordionTab key={index} header={<AccordianHeader lesson={lesson} index={index} />}>
|
<AccordionTab key={index} header={<AccordianHeader lesson={lesson} index={index} />}>
|
||||||
<div className="p-inputgroup flex-1 mt-4">
|
<div className="p-inputgroup flex-1 mt-4">
|
||||||
|
@ -71,7 +71,7 @@ export const updateCourseDraft = async (id, data) => {
|
|||||||
create: draftLessons.map((lesson, index) => ({
|
create: draftLessons.map((lesson, index) => ({
|
||||||
draftId: lesson.draftId,
|
draftId: lesson.draftId,
|
||||||
resourceId: lesson.resourceId,
|
resourceId: lesson.resourceId,
|
||||||
index: index
|
index
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -14,8 +14,6 @@ export function useCoursesQuery() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const hasRequiredProperties = (event, contentIds) => {
|
const hasRequiredProperties = (event, contentIds) => {
|
||||||
// currently no topic tag added
|
|
||||||
// const hasCourseTag = event.tags.some(([tag, value]) => tag === "t" && value === "course");
|
|
||||||
const hasId = event.tags.some(([tag, value]) => tag === "d" && contentIds.includes(value));
|
const hasId = event.tags.some(([tag, value]) => tag === "d" && contentIds.includes(value));
|
||||||
return hasId;
|
return hasId;
|
||||||
};
|
};
|
||||||
|
@ -14,10 +14,9 @@ export function useResourcesQuery() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const hasRequiredProperties = (event, contentIds) => {
|
const hasRequiredProperties = (event, contentIds) => {
|
||||||
const hasPlebDevs = event.tags.some(([tag, value]) => tag === "t" && value === "plebdevs");
|
|
||||||
const hasResource = event.tags.some(([tag, value]) => tag === "t" && value === "resource");
|
const hasResource = event.tags.some(([tag, value]) => tag === "t" && value === "resource");
|
||||||
const hasId = event.tags.some(([tag, value]) => tag === "d" && contentIds.includes(value));
|
const hasId = event.tags.some(([tag, value]) => tag === "d" && contentIds.includes(value));
|
||||||
return hasPlebDevs && hasResource && hasId;
|
return hasResource && hasId;
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchResourcesFromNDK = async () => {
|
const fetchResourcesFromNDK = async () => {
|
||||||
|
@ -14,10 +14,9 @@ export function useWorkshopsQuery() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const hasRequiredProperties = (event, contentIds) => {
|
const hasRequiredProperties = (event, contentIds) => {
|
||||||
const hasPlebDevs = event.tags.some(([tag, value]) => tag === "t" && value === "plebdevs");
|
|
||||||
const hasWorkshop = event.tags.some(([tag, value]) => tag === "t" && value === "workshop");
|
const hasWorkshop = event.tags.some(([tag, value]) => tag === "t" && value === "workshop");
|
||||||
const hasId = event.tags.some(([tag, value]) => tag === "d" && contentIds.includes(value));
|
const hasId = event.tags.some(([tag, value]) => tag === "d" && contentIds.includes(value));
|
||||||
return hasPlebDevs && hasWorkshop && hasId;
|
return hasWorkshop && hasId;
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchWorkshopsFromNDK = async () => {
|
const fetchWorkshopsFromNDK = async () => {
|
||||||
|
@ -27,17 +27,15 @@ export default async function handler(req, res) {
|
|||||||
} else if (req.method === 'PUT') {
|
} else if (req.method === 'PUT') {
|
||||||
try {
|
try {
|
||||||
const { slug } = req.query;
|
const { slug } = req.query;
|
||||||
const { title, summary, image, price, topics } = req.body;
|
const { title, summary, image, price, topics, draftLessons } = req.body;
|
||||||
|
|
||||||
const updatedCourseDraft = await prisma.courseDraft.update({
|
const updatedCourseDraft = await updateCourseDraft(slug, {
|
||||||
where: { id: slug },
|
title,
|
||||||
data: {
|
summary,
|
||||||
title,
|
image,
|
||||||
summary,
|
price,
|
||||||
image,
|
topics,
|
||||||
price,
|
draftLessons
|
||||||
topics,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
res.status(200).json(updatedCourseDraft);
|
res.status(200).json(updatedCourseDraft);
|
||||||
@ -59,4 +57,4 @@ export default async function handler(req, res) {
|
|||||||
res.setHeader('Allow', ['GET', 'PUT', 'DELETE']);
|
res.setHeader('Allow', ['GET', 'PUT', 'DELETE']);
|
||||||
res.status(405).end(`Method ${req.method} Not Allowed`);
|
res.status(405).end(`Method ${req.method} Not Allowed`);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,24 +1,26 @@
|
|||||||
import prisma from "@/db/prisma";
|
import { createCourseDraft } from "@/db/models/courseDraftModels";
|
||||||
|
|
||||||
export default async function handler(req, res) {
|
export default async function handler(req, res) {
|
||||||
if (req.method === 'POST') {
|
if (req.method === 'POST') {
|
||||||
try {
|
try {
|
||||||
const { userId, title, summary, image, price, topics } = req.body;
|
const { userId, title, summary, image, price, topics, draftLessons } = req.body;
|
||||||
|
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
return res.status(400).json({ error: 'userId is required' });
|
return res.status(400).json({ error: 'userId is required' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const courseDraft = await prisma.courseDraft.create({
|
const courseDraft = await createCourseDraft({
|
||||||
data: {
|
userId,
|
||||||
title,
|
title,
|
||||||
summary,
|
summary,
|
||||||
image,
|
image,
|
||||||
price,
|
price,
|
||||||
topics: topics || [],
|
topics: [...new Set([...topics.map(topic => topic.trim().toLowerCase())])],
|
||||||
user: { connect: { id: userId } },
|
draftLessons: draftLessons?.map((lesson, index) => ({
|
||||||
},
|
draftId: lesson.draftId,
|
||||||
include: { draftLessons: true }
|
resourceId: lesson.resourceId,
|
||||||
|
index
|
||||||
|
})) || []
|
||||||
});
|
});
|
||||||
|
|
||||||
res.status(201).json(courseDraft);
|
res.status(201).json(courseDraft);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { getResourceById, updateResource, deleteResource, isResourcePartOfAnyCourse, updateLessonInCourse } from "@/db/models/resourceModels";
|
import { getResourceById, updateResource, deleteResource, } from "@/db/models/resourceModels";
|
||||||
|
|
||||||
export default async function handler(req, res) {
|
export default async function handler(req, res) {
|
||||||
const { slug } = req.query;
|
const { slug } = req.query;
|
||||||
@ -32,13 +32,8 @@ export default async function handler(req, res) {
|
|||||||
}
|
}
|
||||||
} else if (req.method === 'DELETE') {
|
} else if (req.method === 'DELETE') {
|
||||||
try {
|
try {
|
||||||
const isPartOfAnyCourse = await isResourcePartOfAnyCourse(slug);
|
|
||||||
if (isPartOfAnyCourse) {
|
|
||||||
res.status(400).json({ error: 'Resource is part of one or more courses' });
|
|
||||||
} else {
|
|
||||||
await deleteResource(slug);
|
await deleteResource(slug);
|
||||||
res.status(204).end();
|
res.status(204).end();
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(500).json({ error: error.message });
|
res.status(500).json({ error: error.message });
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user