mirror of
https://github.com/AustinKelsay/plebdevs.git
synced 2025-06-06 01:02:04 +00:00
Allow both drafts and published resources to make it into course draft, seperated selectedContentItem in course form into its own component
This commit is contained in:
parent
054adf6869
commit
d1c121e6e8
@ -91,6 +91,12 @@ CREATE TABLE "CourseDraft" (
|
|||||||
CONSTRAINT "CourseDraft_pkey" PRIMARY KEY ("id")
|
CONSTRAINT "CourseDraft_pkey" PRIMARY KEY ("id")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "_CourseDraftToDraft" (
|
||||||
|
"A" TEXT NOT NULL,
|
||||||
|
"B" TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE UNIQUE INDEX "User_pubkey_key" ON "User"("pubkey");
|
CREATE UNIQUE INDEX "User_pubkey_key" ON "User"("pubkey");
|
||||||
|
|
||||||
@ -106,6 +112,12 @@ CREATE UNIQUE INDEX "Resource_noteId_key" ON "Resource"("noteId");
|
|||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE UNIQUE INDEX "CourseDraft_courseId_key" ON "CourseDraft"("courseId");
|
CREATE UNIQUE INDEX "CourseDraft_courseId_key" ON "CourseDraft"("courseId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "_CourseDraftToDraft_AB_unique" ON "_CourseDraftToDraft"("A", "B");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "_CourseDraftToDraft_B_index" ON "_CourseDraftToDraft"("B");
|
||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
ALTER TABLE "User" ADD CONSTRAINT "User_roleId_fkey" FOREIGN KEY ("roleId") REFERENCES "Role"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
ALTER TABLE "User" ADD CONSTRAINT "User_roleId_fkey" FOREIGN KEY ("roleId") REFERENCES "Role"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
@ -138,3 +150,9 @@ ALTER TABLE "CourseDraft" ADD CONSTRAINT "CourseDraft_userId_fkey" FOREIGN KEY (
|
|||||||
|
|
||||||
-- AddForeignKey
|
-- AddForeignKey
|
||||||
ALTER TABLE "CourseDraft" ADD CONSTRAINT "CourseDraft_courseId_fkey" FOREIGN KEY ("courseId") REFERENCES "Course"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
ALTER TABLE "CourseDraft" ADD CONSTRAINT "CourseDraft_courseId_fkey" FOREIGN KEY ("courseId") REFERENCES "Course"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "_CourseDraftToDraft" ADD CONSTRAINT "_CourseDraftToDraft_A_fkey" FOREIGN KEY ("A") REFERENCES "CourseDraft"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "_CourseDraftToDraft" ADD CONSTRAINT "_CourseDraftToDraft_B_fkey" FOREIGN KEY ("B") REFERENCES "Draft"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
@ -70,18 +70,19 @@ model Resource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model Draft {
|
model Draft {
|
||||||
id String @id @default(uuid())
|
id String @id @default(uuid())
|
||||||
userId String
|
userId String
|
||||||
user User @relation(fields: [userId], references: [id])
|
user User @relation(fields: [userId], references: [id])
|
||||||
type String
|
type String
|
||||||
title String
|
title String
|
||||||
summary String
|
summary String
|
||||||
content String
|
content String
|
||||||
image String?
|
image String?
|
||||||
price Int? @default(0)
|
price Int? @default(0)
|
||||||
topics String[]
|
topics String[]
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
courseDrafts CourseDraft[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model CourseDraft {
|
model CourseDraft {
|
||||||
@ -89,6 +90,7 @@ model CourseDraft {
|
|||||||
userId String
|
userId String
|
||||||
user User @relation(fields: [userId], references: [id])
|
user User @relation(fields: [userId], references: [id])
|
||||||
resources Resource[]
|
resources Resource[]
|
||||||
|
drafts Draft[]
|
||||||
title String
|
title String
|
||||||
summary String
|
summary String
|
||||||
image String?
|
image String?
|
||||||
|
@ -76,10 +76,12 @@ const CoursePaymentButton = ({ lnAddress, amount, onSuccess, onError, courseId }
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
label={`Pay ${amount} sats`}
|
label={`${amount} sats`}
|
||||||
onClick={() => setDialogVisible(true)}
|
onClick={() => setDialogVisible(true)}
|
||||||
disabled={!invoice}
|
disabled={!invoice}
|
||||||
severity='info'
|
severity='primary'
|
||||||
|
rounded
|
||||||
|
icon='pi pi-wallet'
|
||||||
className='text-[#f8f8ff] text-sm'
|
className='text-[#f8f8ff] text-sm'
|
||||||
/>
|
/>
|
||||||
<Dialog
|
<Dialog
|
||||||
|
@ -74,11 +74,13 @@ const ResourcePaymentButton = ({ lnAddress, amount, onSuccess, onError, resource
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
label={`Pay ${amount} sats`}
|
label={`${amount} sats`}
|
||||||
icon="pi pi-wallet"
|
icon="pi pi-wallet"
|
||||||
onClick={() => setDialogVisible(true)}
|
onClick={() => setDialogVisible(true)}
|
||||||
disabled={!invoice}
|
disabled={!invoice}
|
||||||
className="p-2 bg-blue-500 text-white rounded"
|
severity='primary'
|
||||||
|
rounded
|
||||||
|
className="text-[#f8f8ff] text-sm"
|
||||||
/>
|
/>
|
||||||
<Dialog
|
<Dialog
|
||||||
visible={dialogVisible}
|
visible={dialogVisible}
|
||||||
|
31
src/components/content/SelectedContentItem.js
Normal file
31
src/components/content/SelectedContentItem.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import React from "react";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { useImageProxy } from "@/hooks/useImageProxy";
|
||||||
|
import { formatUnixTimestamp } from "@/utils/time";
|
||||||
|
|
||||||
|
const SelectedContentItem = ({ content }) => {
|
||||||
|
const { returnImageProxy } = useImageProxy();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full border-2 rounded-lg border-gray-700 p-2 rounded-tr-none rounded-br-none">
|
||||||
|
<div className="flex flex-row gap-4">
|
||||||
|
<Image
|
||||||
|
alt="content thumbnail"
|
||||||
|
src={returnImageProxy(content.image)}
|
||||||
|
width={50}
|
||||||
|
height={50}
|
||||||
|
className="w-[100px] h-[100px] object-cover object-center border-round"
|
||||||
|
/>
|
||||||
|
<div className="flex-1 max-w-[80vw]">
|
||||||
|
<div className="text-lg text-900 font-bold">{content.title}</div>
|
||||||
|
<div className="w-full text-sm text-600 text-wrap">{content.summary}</div>
|
||||||
|
<div className="text-sm pt-6 text-gray-500">
|
||||||
|
{content.published_at ? formatUnixTimestamp(content.published_at) : "not yet published"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SelectedContentItem;
|
@ -1,33 +1,11 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useImageProxy } from "@/hooks/useImageProxy";
|
import { useImageProxy } from "@/hooks/useImageProxy";
|
||||||
import { Button } from "primereact/button";
|
|
||||||
import { formatUnixTimestamp } from "@/utils/time";
|
import { formatUnixTimestamp } from "@/utils/time";
|
||||||
|
import { Button } from "primereact/button";
|
||||||
|
|
||||||
const ContentDropdownItem = ({ content, onSelect, selected }) => {
|
const ContentDropdownItem = ({ content, onSelect }) => {
|
||||||
const { returnImageProxy } = useImageProxy();
|
const { returnImageProxy } = useImageProxy();
|
||||||
console.log('selected:', content);
|
|
||||||
|
|
||||||
if (selected) {
|
|
||||||
return (
|
|
||||||
<div className="w-full border-y-2 border-l-2 rounded-l-lg border-gray-700">
|
|
||||||
<div className="flex flex-row gap-4 p-2">
|
|
||||||
<Image
|
|
||||||
alt="content thumbnail"
|
|
||||||
src={returnImageProxy(content.image)}
|
|
||||||
width={50}
|
|
||||||
height={50}
|
|
||||||
className="w-[100px] h-[100px] object-cover object-center border-round"
|
|
||||||
/>
|
|
||||||
<div className="flex-1">
|
|
||||||
<div className="text-lg text-900 font-bold">{content.title}</div>
|
|
||||||
<div className="min-h-[40px] text-sm text-600 line-clamp-2 break-words">{content.summary}</div>
|
|
||||||
<div className="text-sm pt-6 text-gray-500">{content.published_at ? formatUnixTimestamp(content.published_at) : "not yet published"}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full border-t-2 border-gray-700 py-4">
|
<div className="w-full border-t-2 border-gray-700 py-4">
|
||||||
@ -42,7 +20,9 @@ const ContentDropdownItem = ({ content, onSelect, selected }) => {
|
|||||||
<div className="flex-1 max-w-[80vw]">
|
<div className="flex-1 max-w-[80vw]">
|
||||||
<div className="text-lg text-900 font-bold">{content.title}</div>
|
<div className="text-lg text-900 font-bold">{content.title}</div>
|
||||||
<div className="w-full text-sm text-600 text-wrap">{content.summary}</div>
|
<div className="w-full text-sm text-600 text-wrap">{content.summary}</div>
|
||||||
<div className="text-sm pt-6 text-gray-500">{content.published_at ? formatUnixTimestamp(content.published_at) : "not yet published"}</div>
|
<div className="text-sm pt-6 text-gray-500">
|
||||||
|
{content.published_at ? formatUnixTimestamp(content.published_at) : "not yet published"}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col justify-end">
|
<div className="flex flex-col justify-end">
|
||||||
<Button label="Select" onClick={() => onSelect(content)} />
|
<Button label="Select" onClick={() => onSelect(content)} />
|
||||||
|
@ -17,6 +17,7 @@ import { useDraftsQuery } from "@/hooks/apiQueries/useDraftsQuery";
|
|||||||
import { NDKEvent } from "@nostr-dev-kit/ndk";
|
import { NDKEvent } from "@nostr-dev-kit/ndk";
|
||||||
import { parseEvent } from "@/utils/nostr";
|
import { parseEvent } from "@/utils/nostr";
|
||||||
import ContentDropdownItem from "@/components/content/dropdowns/ContentDropdownItem";
|
import ContentDropdownItem from "@/components/content/dropdowns/ContentDropdownItem";
|
||||||
|
import SelectedContentItem from "@/components/content/SelectedContentItem";
|
||||||
import 'primeicons/primeicons.css';
|
import 'primeicons/primeicons.css';
|
||||||
|
|
||||||
|
|
||||||
@ -29,9 +30,7 @@ const CourseForm = ({ draft = null, isPublished = false }) => {
|
|||||||
const [isPaidCourse, setIsPaidCourse] = useState(draft?.price ? true : false);
|
const [isPaidCourse, setIsPaidCourse] = useState(draft?.price ? true : false);
|
||||||
const [price, setPrice] = useState(draft?.price || 0);
|
const [price, setPrice] = useState(draft?.price || 0);
|
||||||
const [coverImage, setCoverImage] = useState('');
|
const [coverImage, setCoverImage] = useState('');
|
||||||
const [lessons, setLessons] = useState([{ id: uuidv4(), title: 'Select a lesson' }]);
|
const [selectedContent, setSelectedContent] = useState([]);
|
||||||
const [loadingLessons, setLoadingLessons] = useState(true);
|
|
||||||
const [selectedLessons, setSelectedLessons] = useState([]);
|
|
||||||
const [topics, setTopics] = useState(['']);
|
const [topics, setTopics] = useState(['']);
|
||||||
|
|
||||||
const { resources, resourcesLoading, resourcesError } = useResourcesQuery();
|
const { resources, resourcesLoading, resourcesError } = useResourcesQuery();
|
||||||
@ -49,41 +48,6 @@ const CourseForm = ({ draft = null, isPublished = false }) => {
|
|||||||
}
|
}
|
||||||
}, [session]);
|
}, [session]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
console.log('selectedLessons:', selectedLessons);
|
|
||||||
}, [selectedLessons]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchLessons = async () => {
|
|
||||||
if (draft && draft?.resources) {
|
|
||||||
const parsedLessons = await Promise.all(
|
|
||||||
draft.resources.map(async (lesson) => {
|
|
||||||
const parsedLesson = await fetchLessonEventFromNostr(lesson.noteId);
|
|
||||||
return parsedLesson;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
setSelectedLessons([...selectedLessons, ...parsedLessons]);
|
|
||||||
setLoadingLessons(false); // Data is loaded
|
|
||||||
} else {
|
|
||||||
setLoadingLessons(false); // No draft means no lessons to load
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchLessons();
|
|
||||||
}, [draft]); // Only depend on draft
|
|
||||||
|
|
||||||
const fetchLessonEventFromNostr = async (eventId) => {
|
|
||||||
try {
|
|
||||||
await ndk.connect();
|
|
||||||
const fetchedEvent = await ndk.fetchEvent(eventId);
|
|
||||||
if (fetchedEvent) {
|
|
||||||
return parseEvent(fetchedEvent);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
showToast('error', 'Error', `Failed to fetch lesson: ${eventId}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (draft) {
|
if (draft) {
|
||||||
console.log('draft:', draft);
|
console.log('draft:', draft);
|
||||||
@ -92,7 +56,7 @@ const CourseForm = ({ draft = null, isPublished = false }) => {
|
|||||||
setIsPaidCourse(draft.price > 0);
|
setIsPaidCourse(draft.price > 0);
|
||||||
setPrice(draft.price || 0);
|
setPrice(draft.price || 0);
|
||||||
setCoverImage(draft.image);
|
setCoverImage(draft.image);
|
||||||
// setSelectedLessons(draft.resources || []);
|
setSelectedContent(draft.resources.concat(draft.drafts) || []);
|
||||||
setTopics(draft.topics || ['']);
|
setTopics(draft.topics || ['']);
|
||||||
}
|
}
|
||||||
}, [draft]);
|
}, [draft]);
|
||||||
@ -110,26 +74,20 @@ const CourseForm = ({ draft = null, isPublished = false }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Step 1: Create the course draft
|
|
||||||
const courseDraftPayload = {
|
const courseDraftPayload = {
|
||||||
userId: user.id, // Make sure this is set
|
userId: user.id,
|
||||||
title,
|
title,
|
||||||
summary,
|
summary,
|
||||||
image: coverImage,
|
image: coverImage,
|
||||||
price: price || 0,
|
price: price || 0,
|
||||||
topics,
|
topics,
|
||||||
|
resources: selectedContent.filter(content => content.kind === 30023 || content.kind === 30402).map(resource => resource.d),
|
||||||
|
drafts: selectedContent.filter(content => !content.kind).map(draft => draft.id),
|
||||||
};
|
};
|
||||||
|
|
||||||
const courseDraftResponse = await axios.post('/api/courses/drafts', courseDraftPayload);
|
const courseDraftResponse = await axios.post('/api/courses/drafts', courseDraftPayload);
|
||||||
const courseDraftId = courseDraftResponse.data.id;
|
const courseDraftId = courseDraftResponse.data.id;
|
||||||
|
|
||||||
// Step 2: Associate resources with the course draft
|
|
||||||
for (const lesson of selectedLessons) {
|
|
||||||
await axios.put(`/api/resources/${lesson.d}`, {
|
|
||||||
courseDraftId: courseDraftId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
showToast('success', 'Success', 'Course draft saved successfully');
|
showToast('success', 'Success', 'Course draft saved successfully');
|
||||||
router.push(`/course/${courseDraftId}/draft`);
|
router.push(`/course/${courseDraftId}/draft`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -138,50 +96,15 @@ const CourseForm = ({ draft = null, isPublished = false }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const createLessonEvent = (lesson) => {
|
const handleContentSelect = (content) => {
|
||||||
const event = new NDKEvent(ndk);
|
if (!selectedContent.some(item => item.id === content.id)) {
|
||||||
event.kind = lesson.price ? 30402 : 30023;
|
setSelectedContent([...selectedContent, content]);
|
||||||
event.content = lesson.content;
|
|
||||||
event.tags = [
|
|
||||||
['d', lesson.id],
|
|
||||||
['title', lesson.title],
|
|
||||||
['summary', lesson.summary],
|
|
||||||
['image', lesson.image],
|
|
||||||
...lesson.topics.map(topic => ['t', topic]),
|
|
||||||
['published_at', Math.floor(Date.now() / 1000).toString()],
|
|
||||||
];
|
|
||||||
return event;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleLessonChange = (e, index) => {
|
|
||||||
const selectedLessonId = e.value;
|
|
||||||
const selectedLesson = getContentOptions(index).flatMap(group => group.items).find(lesson => lesson.value === selectedLessonId);
|
|
||||||
|
|
||||||
const updatedLessons = lessons.map((lesson, i) =>
|
|
||||||
i === index ? { ...lesson, id: selectedLessonId, title: selectedLesson.label.props.content.title } : lesson
|
|
||||||
);
|
|
||||||
setLessons(updatedLessons);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleLessonSelect = (content) => {
|
|
||||||
setSelectedLessons([...selectedLessons, content]);
|
|
||||||
addLesson();
|
|
||||||
};
|
|
||||||
|
|
||||||
const addLesson = () => {
|
|
||||||
setLessons([...lessons, { id: uuidv4(), title: 'Select a lesson' }]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const removeLesson = (index) => {
|
|
||||||
const updatedLessons = lessons.filter((_, i) => i !== index);
|
|
||||||
const updatedSelectedLessons = selectedLessons.filter((_, i) => i !== index);
|
|
||||||
|
|
||||||
if (updatedLessons.length === 0) {
|
|
||||||
updatedLessons.push({ id: uuidv4(), title: 'Select a lesson' });
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
setLessons(updatedLessons);
|
const removeContent = (index) => {
|
||||||
setSelectedLessons(updatedSelectedLessons);
|
const updatedContent = selectedContent.filter((_, i) => i !== index);
|
||||||
|
setSelectedContent(updatedContent);
|
||||||
};
|
};
|
||||||
|
|
||||||
const addTopic = () => {
|
const addTopic = () => {
|
||||||
@ -198,36 +121,34 @@ const CourseForm = ({ draft = null, isPublished = false }) => {
|
|||||||
setTopics(updatedTopics);
|
setTopics(updatedTopics);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getContentOptions = (index) => {
|
const getContentOptions = () => {
|
||||||
if (resourcesLoading || !resources || workshopsLoading || !workshops || draftsLoading || !drafts) {
|
if (resourcesLoading || !resources || workshopsLoading || !workshops || draftsLoading || !drafts) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const filterContent = (content) => {
|
const filterContent = (content) => {
|
||||||
console.log('contentttttt', content);
|
const contentPrice = content.tags ? (content.tags.find(tag => tag[0] === 'price') ? parseInt(content.tags.find(tag => tag[0] === 'price')[1]) : 0) : (content.price || 0);
|
||||||
// If there is price in content.tags, then it is a paid content 'price' in the 0 index and stringified int in the 1 index
|
|
||||||
const contentPrice = content.tags.find(tag => tag[0] === 'price') ? parseInt(content.tags.find(tag => tag[0] === 'price')[1]) : 0;
|
|
||||||
return isPaidCourse ? contentPrice > 0 : contentPrice === 0;
|
return isPaidCourse ? contentPrice > 0 : contentPrice === 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
const draftOptions = drafts.filter(filterContent).map(draft => ({
|
const draftOptions = drafts.filter(filterContent).map(draft => ({
|
||||||
label: <ContentDropdownItem content={draft} onSelect={(content) => handleLessonSelect(content, index)} selected={lessons[index] && lessons[index].id === draft.id} />,
|
label: draft.title,
|
||||||
value: draft.id
|
value: draft
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const resourceOptions = resources.filter(filterContent).map(resource => {
|
const resourceOptions = resources.filter(filterContent).map(resource => {
|
||||||
const { id, kind, pubkey, content, title, summary, image, published_at, d, topics } = parseEvent(resource);
|
const parsedResource = parseEvent(resource);
|
||||||
return {
|
return {
|
||||||
label: <ContentDropdownItem content={{ id, kind, pubkey, content, title, summary, image, published_at, d, topics }} onSelect={(content) => handleLessonSelect(content, index)} selected={lessons[index] && lessons[index].id === id} />,
|
label: parsedResource.title,
|
||||||
value: id
|
value: parsedResource
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const workshopOptions = workshops.filter(filterContent).map(workshop => {
|
const workshopOptions = workshops.filter(filterContent).map(workshop => {
|
||||||
const { id, kind, pubkey, content, title, summary, image, published_at, d, topics } = parseEvent(workshop);
|
const parsedWorkshop = parseEvent(workshop);
|
||||||
return {
|
return {
|
||||||
label: <ContentDropdownItem content={{ id, kind, pubkey, content, title, summary, image, published_at, d, topics }} onSelect={(content) => handleLessonSelect(content, index)} selected={lessons[index] && lessons[index].id === id} />,
|
label: parsedWorkshop.title,
|
||||||
value: id
|
value: parsedWorkshop
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -247,8 +168,7 @@ const CourseForm = ({ draft = null, isPublished = false }) => {
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
// const lessonOptions = getContentOptions();
|
if (resourcesLoading || workshopsLoading || draftsLoading) {
|
||||||
if (loadingLessons || resourcesLoading || workshopsLoading || draftsLoading) {
|
|
||||||
return <ProgressSpinner />;
|
return <ProgressSpinner />;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -279,29 +199,27 @@ const CourseForm = ({ draft = null, isPublished = false }) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="mt-8 flex-col w-full">
|
<div className="mt-8 flex-col w-full">
|
||||||
<div className="mt-4 flex-col w-full">
|
<div className="mt-4 flex-col w-full">
|
||||||
{selectedLessons.map((lesson, index) => {
|
{selectedContent.map((content, index) => (
|
||||||
return (
|
<div key={content.id} className="flex mt-4">
|
||||||
<div key={lesson.id} className="p-inputgroup flex-1 mt-4">
|
<SelectedContentItem content={content} />
|
||||||
<ContentDropdownItem content={lesson} selected={true} />
|
<Button
|
||||||
<Button icon="pi pi-times" className="p-button-danger" onClick={() => removeLesson(index)} />
|
icon="pi pi-times"
|
||||||
</div>
|
className="p-button-danger rounded-tl-none rounded-bl-none"
|
||||||
)
|
onClick={() => removeContent(index)}
|
||||||
})
|
|
||||||
}
|
|
||||||
{lessons.map((lesson, index) => (
|
|
||||||
<div key={lesson.id} className="p-inputgroup flex-1 mt-4">
|
|
||||||
<Dropdown
|
|
||||||
value={lesson.title}
|
|
||||||
options={getContentOptions(index)}
|
|
||||||
onChange={(e) => handleLessonChange(e, index)}
|
|
||||||
placeholder="Select a Lesson"
|
|
||||||
itemTemplate={(option) => option.label}
|
|
||||||
optionLabel="label"
|
|
||||||
optionGroupLabel="label"
|
|
||||||
optionGroupChildren="items"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
<div className="p-inputgroup flex-1 mt-4">
|
||||||
|
<Dropdown
|
||||||
|
options={getContentOptions()}
|
||||||
|
onChange={(e) => handleContentSelect(e.value)}
|
||||||
|
placeholder="Select Content"
|
||||||
|
itemTemplate={(option) => <ContentDropdownItem content={option.value} onSelect={handleContentSelect} />}
|
||||||
|
optionLabel="label"
|
||||||
|
optionGroupLabel="label"
|
||||||
|
optionGroupChildren="items"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4 flex-col w-full">
|
<div className="mt-4 flex-col w-full">
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
import { PrismaClient } from '@prisma/client';
|
import prisma from "@/db/prisma";
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
|
||||||
|
|
||||||
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, resourceIds } = req.body;
|
const { userId, title, summary, image, price, topics, resources, drafts } = req.body;
|
||||||
|
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
return res.status(400).json({ error: 'userId is required' });
|
return res.status(400).json({ error: 'userId is required' });
|
||||||
@ -20,10 +18,13 @@ export default async function handler(req, res) {
|
|||||||
topics: topics || [],
|
topics: topics || [],
|
||||||
user: { connect: { id: userId } },
|
user: { connect: { id: userId } },
|
||||||
resources: {
|
resources: {
|
||||||
connect: resourceIds ? resourceIds.map(id => ({ id })) : []
|
connect: resources ? resources.map(id => ({ id })) : []
|
||||||
|
},
|
||||||
|
drafts: {
|
||||||
|
connect: drafts ? drafts.map(id => ({ id })) : []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
include: { resources: true }
|
include: { resources: true, drafts: true }
|
||||||
});
|
});
|
||||||
|
|
||||||
res.status(201).json(courseDraft);
|
res.status(201).json(courseDraft);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user